web-dev-qa-db-fra.com

Configuration de Spring Security 3.x pour avoir plusieurs points d'entrée

J'utilisais Spring Security 3.x pour gérer l'authentification des utilisateurs dans mes projets et, jusqu'à présent, cela a parfaitement fonctionné.

J'ai récemment reçu les exigences pour un nouveau projet. Dans ce projet, deux ensembles d'authentification d'utilisateur sont nécessaires: un pour authentifier les employés avec LDAP et un autre pour authentifier le client avec une base de données. Je suis un peu perplexe sur la façon de configurer cela dans Spring Security.

Mon idée initiale était de créer un écran de connexion comportant les champs suivants: -

  • champ de bouton radio - permet aux utilisateurs de choisir s’ils s’agissent d’employés ou de clients.
  • j_username champ utilisateur.
  • j_password champ mot de passe.

Si l'utilisateur sélectionne "employé", je souhaite que Spring Security les authentifie auprès de LDAP, sinon les informations d'identification seront authentifiées auprès de la base de données. Cependant, le problème est que le formulaire sera soumis à /j_spring_security_check et qu'il m'est impossible d'envoyer le champ du bouton radio à mon fournisseur d'authentification personnalisé implémenté. Ma pensée initiale est que j’ai probablement besoin de deux URL de soumission de formulaire plutôt que de compter sur le /j_spring_security_check par défaut. Chaque URL sera gérée par différents fournisseurs d'authentification, mais je ne suis pas sûr de savoir comment configurer cela dans Spring Security.

Je sais que dans Spring Security, je peux configurer l’authentification de secours, par exemple, si l’authentification LDAP échoue, l’authentification de base de données sera utilisée, mais ce n’est pas ce que je recherche dans ce nouveau projet. 

Quelqu'un peut-il partager comment exactement je devrais configurer cela dans Spring Security 3.x?

Je vous remercie.


MISE À JOUR - 28/01/2011 - La technique de EasyAngel

J'essaie de faire ce qui suit: -

  • L'identifiant du formulaire employé est envoyé à /j_spring_security_check_for_employee
  • L'identifiant du formulaire client est envoyé à /j_spring_security_check_for_customer

La raison pour laquelle je souhaite 2 connexions de formulaire différentes est pour me permettre de gérer l'authentification différemment en fonction de l'utilisateur, au lieu d'effectuer une authentification de secours. Il est possible que l'employé et le client aient le même identifiant, dans mon cas.

J'ai intégré l'idée de @ EasyAngel, mais je dois remplacer certaines classes obsolètes. Le problème auquel je suis confronté actuellement n’est ni l’un ni l’autre des processus de filtrage. Les URL semblent être enregistrées dans Spring Security, car je continue d’obtenir Error 404: SRVE0190E: File not found: /j_spring_security_check_for_employee. Mon instinct est que le bean springSecurityFilterChain n'est pas câblé correctement. Par conséquent, mes filtres personnalisés ne sont pas utilisés du tout.

En passant, j'utilise WebSphere et la propriété com.ibm.ws.webcontainer.invokefilterscompatibility=true est définie sur le serveur. Je peux frapper le /j_spring_security_check par défaut sans problème.

Voici ma configuration de sécurité complète: -

<?xml version="1.0" encoding="UTF-8"?>
<beans xmlns="http://www.springframework.org/schema/beans" xmlns:sec="http://www.springframework.org/schema/security" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
    xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd
                        http://www.springframework.org/schema/security http://www.springframework.org/schema/security/spring-security.xsd">

    <sec:http auto-config="true">
        <sec:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?login_error=1" default-target-url="/welcome.jsp"
            always-use-default-target="true" />
        <sec:logout logout-success-url="/login.jsp" />
        <sec:intercept-url pattern="/employee/**" access="ROLE_EMPLOYEE" />
        <sec:intercept-url pattern="/customer/**" access="ROLE_CUSTOMER" />
        <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
    </sec:http>

    <bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
        <sec:filter-chain-map path-type="ant">
            <sec:filter-chain pattern="/**" filters="authenticationProcessingFilterForEmployee, authenticationProcessingFilterForCustomer" />
        </sec:filter-chain-map>
    </bean>

    <bean id="authenticationProcessingFilterForEmployee" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManagerForEmployee" />
        <property name="filterProcessesUrl" value="/j_spring_security_check_for_employee" />
    </bean>

    <bean id="authenticationProcessingFilterForCustomer" class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManagerForCustomer" />
        <property name="filterProcessesUrl" value="/j_spring_security_check_for_customer" />
    </bean>

    <bean id="authenticationManagerForEmployee" class="org.springframework.security.authentication.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="employeeCustomAuthenticationProvider" />
            </list>
        </property>
    </bean>

    <bean id="authenticationManagerForCustomer" class="org.springframework.security.authentication.ProviderManager">
        <property name="providers">
            <list>
                <ref bean="customerCustomAuthenticationProvider" />
            </list>
        </property>
    </bean>

    <bean id="employeeCustomAuthenticationProvider" class="ss.EmployeeCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.EmployeeUserDetailsService"/>
        </property>
    </bean>

    <bean id="customerCustomAuthenticationProvider" class="ss.CustomerCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.CustomerUserDetailsService"/>
        </property>
    </bean>

    <sec:authentication-manager>
        <sec:authentication-provider ref="employeeCustomAuthenticationProvider" />
        <sec:authentication-provider ref="customerCustomAuthenticationProvider" />
    </sec:authentication-manager>

</beans>

Je commence une prime ici parce que je n'arrive pas à faire fonctionner cela pendant plusieurs jours déjà… la frustration est la Parole. J'espère que quelqu'un va signaler le (s) problème (s), ou si vous pouvez me montrer un moyen meilleur ou plus propre de le gérer (en code). 

J'utilise Spring Security 3.x. 

Je vous remercie.


MISE À JOUR du 29/01/2011 - @ technique de Ritesh

D'accord, j'ai réussi à faire en sorte que l'approche de @ Ritesh fonctionne très étroitement avec ce que je voulais. J'ai le bouton radio qui permet à l'utilisateur de choisir s'il est client ou employé. Il semble que cette approche fonctionne assez bien, avec un problème ... 

  • Si un employé se connecte avec les informations d'identification correctes, il est autorisé à ... TRAVAIL TEL QUE PRÉVU .
  • Si l'employé se connecte avec des informations d'identification incorrectes, il n'est pas autorisé à ... TRAVAILLE COMME ATTENDU .
  • Si le client se connecte avec les informations d'identification correctes, il est autorisé à ... TRAVAILLER COMME PRÉVU .
  • Si le client se connecte avec des informations d'identification incorrectes, l'authentification revient à l'authentification de l'employé ... NE FONCTIONNE PAS . Cela est risqué, car si je sélectionne l'authentification du client et que je lui attribue les informations d'identification de l'employé, elle permettra également à l'utilisateur d'entrer et ce n'est pas ce que je veux.
    <sec:http auto-config="false" entry-point-ref="loginUrlAuthenticationEntryPoint">
        <sec:logout logout-success-url="/login.jsp"/>
        <sec:intercept-url pattern="/employee/**" access="ROLE_EMPLOYEE"/>
        <sec:intercept-url pattern="/customer/**" access="ROLE_CUSTOMER"/>
        <sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY"/>

        <sec:custom-filter position="FORM_LOGIN_FILTER" ref="myAuthenticationFilter"/>
    </sec:http>


    <bean id="myAuthenticationFilter" class="ss.MyAuthenticationFilter">
        <property name="authenticationManager" ref="authenticationManager"/>
        <property name="authenticationFailureHandler" ref="failureHandler"/>
        <property name="authenticationSuccessHandler" ref="successHandler"/>
    </bean>

    <bean id="loginUrlAuthenticationEntryPoint"
          class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
        <property name="loginFormUrl" value="/login.jsp"/>
    </bean>

    <bean id="successHandler"
          class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
        <property name="defaultTargetUrl" value="/welcome.jsp"/>
        <property name="alwaysUseDefaultTargetUrl" value="true"/>
    </bean>

    <bean id="failureHandler"
          class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
        <property name="defaultFailureUrl" value="/login.jsp?login_error=1"/>
    </bean>


    <bean id="employeeCustomAuthenticationProvider" class="ss.EmployeeCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.EmployeeUserDetailsService"/>
        </property>
    </bean>

    <bean id="customerCustomAuthenticationProvider" class="ss.CustomerCustomAuthenticationProvider">
        <property name="userDetailsService">
            <bean class="ss.CustomerUserDetailsService"/>
        </property>
    </bean>


    <sec:authentication-manager alias="authenticationManager">
        <sec:authentication-provider ref="customerCustomAuthenticationProvider"/>
        <sec:authentication-provider ref="employeeCustomAuthenticationProvider"/>
    </sec:authentication-manager>
</beans>

Voici ma configuration mise à jour. Il faut que ce soit un très petit Tweak que je dois faire pour empêcher le repli de l'authentification, mais je n'arrive pas à le comprendre maintenant. 

Je vous remercie.

MISE À JOUR - SOLUTION à la technique de Ritesh

D'accord, je pense avoir résolu le problème ici. Au lieu d’avoir EmployeeCustomAuthenticationProvider à compter sur la valeur par défaut UsernamePasswordAuthenticationToken, j’ai créé EmployeeUsernamePasswordAuthenticationToken pour elle, tout comme celle que j’ai créée CustomerUsernamePasswordAuthenticationToken pour CustomerCustomAuthenticationProvider. Ces fournisseurs remplaceront alors la supports(): -

Classe CustomerCustomAuthenticationProvider

@Override
public boolean supports(Class<? extends Object> authentication) {
    return (CustomerUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

EmployeeCustomAuthenticationProvider class

@Override
public boolean supports(Class<? extends Object> authentication) {
    return (EmployeeUsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication));
}

Classe MyAuthenticationFilter

public Authentication attemptAuthentication(HttpServletRequest request, HttpServletResponse response) throws AuthenticationException {

    ...

    UsernamePasswordAuthenticationToken authRequest = null;

    if ("customer".equals(request.getParameter("radioAuthenticationType"))) {
        authRequest = new CustomerUsernamePasswordAuthenticationToken(username, password);

    }
    else {
        authRequest = new EmployeeUsernamePasswordAuthenticationToken(username, password);
    }

    setDetails(request, authRequest);

    return super.getAuthenticationManager().authenticate(authRequest);
}

... et WALAA! Cela fonctionne parfaitement maintenant après plusieurs jours de frustration! 

J'espère que ce message pourra aider quelqu'un qui fait la même chose que moi.

60
limc

Vous n'avez pas besoin de créer /j_spring_security_check_for_employee et /j_security_check_for_customerfilterProcessingUrl

Le paramètre par défaut fonctionnera parfaitement avec l’idée du champ de bouton radio.

Dans la connexion personnalisée LoginFilter, vous devez créer différents jetons pour l'employé et le client.

Voici les étapes:

  1. Utilisez la valeur par défaut UsernamePasswordAuthenticationToken pour la connexion de l'employé.

  2. Créez CustomerAuthenticationToken pour la connexion client. Étendez AbstractAuthenticationToken de sorte que son type de classe soit distinct de UsernamePasswordAuthenticationToken.

  3. Définir un filtre de connexion personnalisé:

    <security:http>
        <security:custom-filter position="FORM_LOGIN_FILTER" ref="customFormLoginFilter" />
    </security:http>
    
  4. Dans customFormLoginFilter, remplacez attemptAuthentication comme suit (pseudo-code):

    if (radiobutton_param value employee) {
        UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(username, password);
        setDetails(whatever);
        return getAuthenticationManager().authenticate(authRequest);
    } else if (radiobutton_param value customer) {
        CustomerAuthenticationToken authRequest = new CustomerAuthenticationToken(username, password);
        setDetails(whatever);
        return getAuthenticationManager().authenticate(authRequest);
    }
    
  5. Remplacez la méthode supports dans EmployeeCustomAuthenticationProvider par UsernamePasswordAuthenticationToken.

  6. Remplacez la méthode supports dans CustomerCustomAuthenticationProvider par CustomerAuthenticationToken.

    @Override
    public boolean supports(Class<?> authentication) {
        return (CustomerAuthenticationToken.class.isAssignableFrom(authentication));
    }
    
  7. Utilisez les deux fournisseurs dans authentication-manager:

    <security:authentication-manager alias="authenticationManager">
        <security:authentication-provider ref='employeeCustomAuthenticationProvider ' />
        <security:authentication-provider ref='customerCustomAuthenticationProvider ' />
    </security:authentication-manager>
    
23
Ritesh

Vous pouvez définir plusieurs filtres AuthenticationProcessingFilter. Chacun d'entre eux peut avoir une URL différente comme/j_security_check_for_employee et/j_security_check_for_customer . Voici un exemple de contexte d'application de sécurité illustrant cette idée:

<bean id="myfilterChainProxy" class="org.springframework.security.util.FilterChainProxy">
     <security:filter-chain-map pathType="ant">
         <security:filter-chain pattern="/**" filters="authenticationProcessingFilterForCustomer, authenticationProcessingFilterForEmployee, ..." />
     </security:filter-chain-map>
</bean>


<bean id="authenticationProcessingFilterForCustomer" class="org.springframework.security.web.authentication.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManagerForCustomer"/>
    <property name="filterProcessesUrl" value="/j_security_check_for_customer"/>
</bean>

<bean id="authenticationProcessingFilterForEmployee" class="org.springframework.security.web.authentication.AuthenticationProcessingFilter">
    <property name="authenticationManager" ref="authenticationManagerForEmployee"/>
    <property name="filterProcessesUrl" value="/j_security_check_for_employee"/>
</bean>

<bean id="authenticationManagerForCustomer" class="org.springframework.security.authentication.ProviderManager">
    <property name="providers">
        <list>
            <bean class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
                <property name="userDetailsService">
                    <ref bean="customerUserDetailsServiceThatUsesDB"/>
                </property>
            </bean>
        </list>
    </property>
</bean>

<bean id="authenticationManagerForEmployee" class="org.springframework.security.authentication.ProviderManager">
    <property name="providers">
        <list>
            <bean class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
                <property name="userDetailsService">
                    <ref bean="employeeUserDetailsServiceThatUsesLDAP"/>
                </property>
            </bean>
        </list>
    </property>
</bean>

Comme vous pouvez le constater, dans ce scénario, vous avez également différents UserDetailServices - pour l’authentification de base de données et le protocole LDAP. 

Je pense que c'est une bonne idée d'avoir différentes URL d'authentification pour les clients et les employés (surtout s'ils utilisent des stratégies d'authentification différentes). Vous pouvez même avoir différentes pages de connexion pour eux.

4
tenshi

c'est encore moi :) Peux-tu essayer d'utiliser des filtres comme celui-ci:

<sec:http auto-config="true">
    ...
    <sec:custom-filter ref="authenticationProcessingFilterForCustomer" after="FIRST"/>
    <sec:custom-filter ref="authenticationProcessingFilterForEmployee" after="FIRST"/>
</sec:http>

au lieu de définir bean springSecurityFilterChain.

0
tenshi

Vous pouvez stocker ces informations dans la base de données. Par exemple, vous pouvez avoir une colonne appelée ldap_auth dans la table Users. Vous pouvez regarder mon autre réponse (à titre d'exemple):

Exemple de formulaire de connexion de printemps

Si vous examinez attentivement la classe UserService, vous remarquerez que je teste cet indicateur LDAP et que je récupère le mot de passe de l'utilisateur à partir de LDAP ou de la base de données.

0
tenshi