How to dynamically decide <intercept-url> access attribute value in Spring Security?

43,222

Solution 1

The FilterInvocationSecurityMetadataSourceParser class in Spring-security (try Ctrl/Cmd+Shift+T in STS with the source code) parses the intercept-url tags and creates instances of ExpressionBasedFilterInvocationSecurityMetadataSource, that extends DefaultFilterInvocationSecurityMetadataSource that implements FilterInvocationSecurityMetadataSource that extends SecurityMetadataSource.

What I did is to create a custom class that implements FilterInvocationSecurityMetadataSource, OptionsFromDataBaseFilterInvocationSecurityMetadataSource. I used DefaultFilterInvocationSecurityMetadataSource as base to use urlMatcher, to implement the support() method and something like that.

Then you must to implement these methods:

  • Collection getAttributes(Object object), where you can access to database, searching for the 'object' being secured (normally the URL to access) to obtain the allowed ConfigAttribute's (normally the ROLE's)

  • boolean supports(Class clazz)

  • Collection getAllConfigAttributes()

Be careful with the later, because it's called at startup and maybe is not well configured at this time (I mean, with the datasources or persistence context autowired, depending on what are you using). The solution in a web environment is to configure the contextConfigLocation in the web.xml to load the applicationContext.xml before the applicationContext-security.xml

The final step is to customize the applicationContext-security.xml to load this bean.

For doing that, I used regular beans in this file instead of the security namespace:

    <beans:bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
    <filter-chain-map path-type="ant">
        <filter-chain pattern="/images/*" filters="none" />
        <filter-chain pattern="/resources/**" filters="none" />
        <filter-chain pattern="/**" filters="
        securityContextPersistenceFilter,
        logoutFilter,
        basicAuthenticationFilter,
        exceptionTranslationFilter,
        filterSecurityInterceptor" 
    />
    </filter-chain-map>
</beans:bean>

You have to define all the related beans. For instance:

    <beans:bean id="filterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor">
    <beans:property name="authenticationManager" ref="authenticationManager"></beans:property>
    <beans:property name="accessDecisionManager" ref="affirmativeBased"></beans:property>
    <beans:property name="securityMetadataSource" ref="optionsFromDataBaseFilterInvocationSecurityMetadataSource"></beans:property>
    <beans:property name="validateConfigAttributes" value="true"/></beans:bean>

I know that is not a well explained answer, but it's not as difficult as it seems.

Just use the spring source as base and you will obtain what you want.

Debugging with the data in your database, will help you a lot.

Solution 2

Actually, spring security 3.2 do not encourage to do this according to http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-dynamic-url-metadata

but, it is possible (but not elegant) using http element in namespace with a custom accessDecisionManager..

The config should be:

<http pattern="/login.action" security="none"/>
<http pattern="/media/**" security="none"/>

<http access-decision-manager-ref="accessDecisionManager" >
    <intercept-url pattern="/**" access="ROLE_USER"/>
    <form-login login-page="/login.action"
                authentication-failure-url="/login?error=1"
                default-target-url="/console.action"/>
    <logout invalidate-session="true" delete-cookies="JSESIONID"/>
    <session-management session-fixation-protection="migrateSession">
        <concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login.action"/>
    </session-management>

    <!-- NO ESTA FUNCIONANDO, los tokens no se ponen en el request!
    <csrf />
     -->

</http>
<authentication-manager>
    <authentication-provider>
        <user-service>
            <user name="test" password="test" authorities="ROLE_USER" />
        </user-service>
    </authentication-provider>
</authentication-manager>

<beans:bean id="accessDecisionManager" class="openjsoft.core.services.security.auth.CustomAccessDecisionManager">
    <beans:property name="allowIfAllAbstainDecisions" value="false"/>
    <beans:property name="decisionVoters">
    <beans:list>
        <beans:bean class="org.springframework.security.access.vote.RoleVoter"/>
    </beans:list>
    </beans:property>
</beans:bean>

The CustomAccessDecisionManager should be...

public class CustomAccessDecisionManager extends AbstractAccessDecisionManager  {
...

public void decide(Authentication authentication, Object filter,
        Collection<ConfigAttribute> configAttributes)
        throws AccessDeniedException, InsufficientAuthenticationException {

  if ((filter == null) || !this.supports(filter.getClass())) {
        throw new IllegalArgumentException("Object must be a FilterInvocation");
    }

    String url = ((FilterInvocation) filter).getRequestUrl();
    String contexto = ((FilterInvocation) filter).getRequest().getContextPath();

    Collection<ConfigAttribute> roles = service.getConfigAttributesFromSecuredUris(contexto, url);



    int deny = 0;

    for (AccessDecisionVoter voter : getDecisionVoters()) {
        int result = voter.vote(authentication, filter, roles);

        if (logger.isDebugEnabled()) {
            logger.debug("Voter: " + voter + ", returned: " + result);
        }

        switch (result) {
        case AccessDecisionVoter.ACCESS_GRANTED:
            return;

        case AccessDecisionVoter.ACCESS_DENIED:

            deny++;

            break;

        default:
            break;
        }
    }

    if (deny > 0) {
        throw new AccessDeniedException(messages.getMessage("AbstractAccessDecisionManager.accessDenied",
                "Access is denied"));
    }

    // To get this far, every AccessDecisionVoter abstained
    checkAllowIfAllAbstainDecisions();
}

...
}

Where getConfigAttributesFromSecuredUris retrieve form DB de roles for the specific URL

Solution 3

I have kind of the same problem, basically I'd like to keep separate the list of intercept-url from the other springsecurity configuration section, the first to belong to the application configuration the latter to the product (core, plugin) configuration.

There is a proposal in the JIRA of spring, concerning this problem.

I don't want to give up to use the springsecurity namespace, so I was thinking to some possible solutions in order to deal with this.

In order to have the list of intercept-url dynamically created you have to inject the securitymetadatasource object in the FilterSecurityInterceptor. Using springsecurity schema the instance of FilterSecurityInterceptor is created by the HttpBuilder class and there is no way to pass the securitymetadatasource as property defined in the schema configuration file, as less as using kind of workaround, which could be:

  • Define a custom filter, to be executed before FilterSecurityInterceptor, in this filter retrieving the instance FilterSecurityInterceptor (assuming a unique http section is defined) by the spring context and inject there the securitymetadatasource instance;
  • The same as above but in a HandlerInterceptor.

What do you think?

Solution 4

This is how it can be done in Spring Security 3.2:

@Configuration
@EnableWebMvcSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Bean
    public SecurityConfigDao securityConfigDao() {
        SecurityConfigDaoImpl impl = new SecurityConfigDaoImpl() ;
        return impl ;
    }



    @Override
    protected void configure(HttpSecurity http) throws Exception {
        /* get a map of patterns and authorities */
        Map<String,String> viewPermissions = securityConfigDao().viewPermissions() ;

         ExpressionUrlAuthorizationConfigurer<HttpSecurity>.ExpressionInterceptUrlRegistry interceptUrlRegistry = http
        .authorizeRequests().antMatchers("/publicAccess/**")
        .permitAll(); 

        for (Map.Entry<String, String> entry: viewPermissions.entrySet()) {
            interceptUrlRegistry.antMatchers(entry.getKey()).hasAuthority(entry.getValue());
        }

        interceptUrlRegistry.anyRequest().authenticated()
        .and()
        ...
        /* rest of the configuration */
    }
}

Solution 5

A simple solution that works for me.

<intercept-url pattern="/**/**" access="#{@customAuthenticationProvider.returnStringMethod}" />
<intercept-url pattern="/**" access="#{@customAuthenticationProvider.returnStringMethod}" />

customAuthenticationProvider is a bean

<beans:bean id="customAuthenticationProvider"
    class="package.security.CustomAuthenticationProvider" />

in CustomAuthenticationProvider class create method:

public synchronized String getReturnStringMethod()
{
    //get data from database (call your method)

    if(condition){
        return "IS_AUTHENTICATED_ANONYMOUSLY";
    }
    return "ROLE_ADMIN,ROLE_USER";
}
Share:
43,222

Related videos on Youtube

Cracker
Author by

Cracker

Updated on August 08, 2020

Comments

  • Cracker
    Cracker almost 4 years

    In Spring Security we use the intercept-url tag to define the access for URLs as below:

    <intercept-url pattern="/**" access="ROLE_ADMIN" />
    <intercept-url pattern="/student" access="ROLE_STUDENT" />
    

    This is hard coded in applicationContext-security.xml. I want to read the access values from a database table instead. I have defined my own UserDetailsService and I read the roles for the logged in user from the database. How do I assign these roles to the URL patterns during runtime?

  • SunJCarkeY
    SunJCarkeY almost 10 years
    hi I'm trying to achieve same thing but with struts2. Went through Spring Security 3.1 but couldn't get enough ideas about integrating with struts2. Would you mind giving some basic ideas to integrate with struts2?
  • ajaristi
    ajaristi about 9 years
    This example is under an old version of spring. How I can do it under spring-security 3.2?
  • franta kocourek
    franta kocourek about 8 years
    I ended up with the very same solution. It's a pity there is no easy way how to split the definition.