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: -
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: -
/j_spring_security_check_for_employee
/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 ...
<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.
Vous n'avez pas besoin de créer /j_spring_security_check_for_employee
et /j_security_check_for_customer
filterProcessingUrl
.
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:
Utilisez la valeur par défaut UsernamePasswordAuthenticationToken
pour la connexion de l'employé.
Créez CustomerAuthenticationToken
pour la connexion client. Étendez AbstractAuthenticationToken
de sorte que son type de classe soit distinct de UsernamePasswordAuthenticationToken
.
Définir un filtre de connexion personnalisé:
<security:http>
<security:custom-filter position="FORM_LOGIN_FILTER" ref="customFormLoginFilter" />
</security:http>
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);
}
Remplacez la méthode supports
dans EmployeeCustomAuthenticationProvider
par UsernamePasswordAuthenticationToken
.
Remplacez la méthode supports
dans CustomerCustomAuthenticationProvider
par CustomerAuthenticationToken
.
@Override
public boolean supports(Class<?> authentication) {
return (CustomerAuthenticationToken.class.isAssignableFrom(authentication));
}
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>
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 UserDetailService
s - 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.
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
.
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.