web-dev-qa-db-fra.com

Comment décider dynamiquement de la valeur de l'attribut d'accès <intercept-url> dans Spring Security?

Dans Spring Security, nous utilisons la balise intercept-url pour définir l'accès aux URL comme ci-dessous:

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

Ceci est codé en dur dans applicationContext-security.xml. Je souhaite plutôt lire les valeurs d'accès à partir d'une table de base de données. J'ai défini mon propre UserDetailsService et j'ai lu les rôles de l'utilisateur connecté dans la base de données. Comment attribuer ces rôles aux modèles d'URL pendant l'exécution?

26
Cracker

La classe FilterInvocationSecurityMetadataSourceParser dans Spring-security (essayez Ctrl/Cmd + Shift + T dans STS avec le code source) analyse les balises intercept-url et crée des instances de ExpressionBasedFilterInvocationSecurityMetadataSource, qui étend DefaultFilterInvocationSecurityMetadataSourceSecuritySecuritySourceSecuritySource.

Ce que j'ai fait, c'est de créer une classe personnalisée qui implémente FilterInvocationSecurityMetadataSource, OptionsFromDataBaseFilterInvocationSecurityMetadataSource. J'ai utilisé DefaultFilterInvocationSecurityMetadataSource comme base pour utiliser urlMatcher, pour implémenter la méthode support () et quelque chose comme ça.

Ensuite, vous devez implémenter ces méthodes:

  • Collection getAttributes (objet Object), où vous pouvez accéder à la base de données, en recherchant "l'objet" sécurisé (normalement l'URL à accéder) pour obtenir le ConfigAttribute autorisé (normalement le ROLE)

  • supports booléens (classe clazz)

  • Collection getAllConfigAttributes ()

Soyez prudent avec ce dernier, car il est appelé au démarrage et n'est peut-être pas bien configuré pour le moment (je veux dire, avec les sources de données ou le contexte de persistance automatiquement câblé, selon ce que vous utilisez). La solution dans un environnement Web consiste à configurer contextConfigLocation dans le fichier web.xml pour charger le fichier applicationContext.xml avant le fichier applicationContext-security.xml

La dernière étape consiste à personnaliser le fichier applicationContext-security.xml pour charger ce bean.

Pour ce faire, j'ai utilisé des beans réguliers dans ce fichier au lieu de l'espace de noms de sécurité:

    <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>

Vous devez définir tous les beans associés. Par exemple:

    <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>

Je sais que ce n'est pas une réponse bien expliquée, mais ce n'est pas aussi difficile qu'il y paraît.

Utilisez simplement la source à ressort comme base et vous obtiendrez ce que vous voulez.

Le débogage avec les données de votre base de données vous aidera beaucoup.

21
jbbarquero

En fait, Spring Security 3.2 n'encourage pas à le faire selon http://docs.spring.io/spring-security/site/docs/3.2.x/reference/htmlsingle/faq.html#faq-dynamic -url-metadata

mais, il est possible (mais pas élégant) d'utiliser l'élément http dans l'espace de noms avec un accessDecisionManager personnalisé.

La configuration doit être:

<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>

Le CustomAccessDecisionManager doit être ...

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();
}

...
}

Où getConfigAttributesFromSecuredUris récupère les rôles de base de données de formulaire pour l'URL spécifique

4
Hugo Robayo

J'ai un peu le même problème, je voudrais essentiellement séparer la liste des intercept-url de l'autre section de configuration de springsecurity, la première à appartenir à la configuration de l'application, la dernière à la configuration du produit (core, plugin).

Il y a une proposition dans le JIRA du printemps, concernant ce problème.

Je ne veux pas renoncer à utiliser l'espace de noms springsecurity, alors je réfléchissais à des solutions possibles pour y faire face.

Pour que la liste des intercept-url soit créée dynamiquement, vous devez injecter l'objet securitymetadatasource dans FilterSecurityInterceptor. En utilisant le schéma springsecurity, l'instance de FilterSecurityInterceptor est créée par la classe HttpBuilder et il n'y a aucun moyen de transmettre la securitymetadatasource en tant que propriété définie dans le fichier de configuration du schéma, autant que d'utiliser un type de solution de contournement, qui pourrait être:

  • Définissez un filtre personnalisé, à exécuter avant FilterSecurityInterceptor, dans ce filtre récupérant l'instance FilterSecurityInterceptor (en supposant qu'une section http unique est définie) par le contexte de ressort et y injectez l'instance securitymetadatasource;
  • Comme ci-dessus mais dans un HandlerInterceptor.

Qu'est-ce que tu penses?

2
Enrico Giurin

Une solution simple qui fonctionne pour moi.

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

customAuthenticationProvider est un bean

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

dans la méthode de création de classe CustomAuthenticationProvider:

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

    if(condition){
        return "IS_AUTHENTICATED_ANONYMOUSLY";
    }
    return "ROLE_ADMIN,ROLE_USER";
}
1
user1403588

C'est la solution que j'ai appliquée afin de séparer la liste des entrées d'URL d'interception de l'autre configuration de sécurité Spring.

<security:custom-filter ref="parancoeFilterSecurityInterceptor"
        before="FILTER_SECURITY_INTERCEPTOR" />
........

<bean id="parancoeFilterSecurityInterceptor" class="org.springframework.security.web.access.intercept.FilterSecurityInterceptor" >  
    <property name="authenticationManager" ref="authenticationManager"/>
    <property name="accessDecisionManager" ref="accessDecisionManager"/>
    <property name="securityMetadataSource" ref="securityMetadataSource"/>
</bean>

Le bean securityMetadataSource peut être placé soit dans le même fichier de configuration, soit dans un autre fichier de configuration.

<security:filter-security-metadata-source
    id="securityMetadataSource" use-expressions="true">
    <security:intercept-url pattern="/admin/**"
        access="hasRole('ROLE_ADMIN')" />
</security:filter-security-metadata-source> 

Bien sûr, vous pouvez décider d'implémenter votre propre bean securityMetadataSource en implémentant l'interface FilterInvocationSecurityMetadataSource. Quelque chose comme ça:

<bean id="securityMetadataSource" class="mypackage.MyImplementationOfFilterInvocationSecurityMetadataSource" />

J'espère que cela t'aides.

1
Enrico Giurin

Voici comment cela peut être fait dans 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 */
    }
}
0
Ritesh