I have been using Spring Security 3.x for handling user authentication for my projects, and so far, it has worked flawlessly.
I recently received the requirements for a new project. In this project, it requires 2 sets of user authentication: one to authenticate employees against LDAP, and another to authenticate customer against database. I'm a little stumped on how to configure that in Spring Security.
My initial idea was to create a login screen that has the following fields:-
j_username
user field.j_password
password field.If the user selects "employee", then I want Spring Security to authenticate them against LDAP, otherwise the credential will be authenticated against database. However, the problem is the form will be submitted to /j_spring_security_check
and there's no way for me to send the radio button field to my implemented custom authentication provider. My initial thought is I probably need two form submission URLs rather than relying on the default /j_spring_security_check
. Each URL will be handled by different authentication providers, but I'm not sure how to configure that in Spring Security.
I know in Spring Security, I can configure fall back authentication, for example if LDAP authentication fails, then it will fall back to database authentication, but this is not what I'm shooting for in this new project.
Can someone share how exactly I should configure this in Spring Security 3.x?
Thank you.
UPDATE - 01-28-2011 - @EasyAngel's technique
I'm trying to do the following:-
/j_spring_security_check_for_employee
/j_spring_security_check_for_customer
The reason I want 2 different form logins is to allow me to handle the authentication differently based on the user, instead of doing a fall-back authentication. It is possible that employee and customer have same user ID, in my case.
I incorporated @EasyAngel's idea, but have to replace some deprecated classes. The problem I'm currently facing is neither filter processes URLS seem registered in Spring Security because I keep getting Error 404: SRVE0190E: File not found: /j_spring_security_check_for_employee
. My gut feeling is the springSecurityFilterChain
bean is not wired properly, thus my custom filters are not used at all.
By the way, I'm using WebSphere and I do have com.ibm.ws.webcontainer.invokefilterscompatibility=true
property set in the server. I'm able to hit the default /j_spring_security_check
without problem.
Here's my complete security configuration:-
<?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>
I'm starting a bounty here because I can't seem to get this working for several days already... frustration is the word. I'm hoping someone will point out the problem(s), or if you can show me a better or cleaner way to handle this (in code).
I'm using Spring Security 3.x.
Thank you.
UPDATE 01-29-2011 - @Ritesh's technique
Okay, I managed to get @Ritesh's approach to work very closely to what I wanted. I have the radiobutton that allows user to select whether they are customer or employee. It seems like this approach is working fairly well, with one problem...
<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>
Here's my updated configuration. It has to be some really small tweak I need to do to prevent the authentication fall back but I can't seem to figure it out now.
Thank you.
UPDATE - SOLUTION to @Ritesh's technique
Okay, I think I have solved the problem here. Instead of having EmployeeCustomAuthenticationProvider
to rely on the default UsernamePasswordAuthenticationToken
, I created EmployeeUsernamePasswordAuthenticationToken
for it, just like the one I created CustomerUsernamePasswordAuthenticationToken
for CustomerCustomAuthenticationProvider
. These providers will then override the supports()
:-
CustomerCustomAuthenticationProvider class
@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)); }
MyAuthenticationFilter class
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); }
... and WALAA! It works perfectly now after several days of frustration!
Hopefully, this post will be able to help somebody who is doing the same thing as I am here.
When using Java configuration, the way to define multiple security realms is to have multiple @Configuration classes that extend the WebSecurityConfigurerAdapter base class – each with its own security configuration. These classes can be static and placed inside the main config.
AuthenticationEntryPoint is used to send an HTTP response that requests credentials from a client. Sometimes a client will proactively include credentials such as a username/password to request a resource.
Using @Secured Annotation. The @Secured annotation is used to specify a list of roles on a method. So, a user only can access that method if she has at least one of the specified roles.
There are basically 2 ways to implement spring security. through bean configuration in . xml files and other by using Annotations. Annotation based method is easy to use in long term as it is less ambiguous.
You don't need to create /j_spring_security_check_for_employee
and /j_security_check_for_customer
filterProcessingUrl
.
The default one will work just fine with radio button field idea.
In the custom login LoginFilter
, you need to create different tokens for employee and customer.
Here are the steps:
Use default UsernamePasswordAuthenticationToken
for employee login.
Create CustomerAuthenticationToken
for customer login. Extend AbstractAuthenticationToken
so that its class type is distinct from UsernamePasswordAuthenticationToken
.
Define a custom login filter:
<security:http> <security:custom-filter position="FORM_LOGIN_FILTER" ref="customFormLoginFilter" /> </security:http>
In customFormLoginFilter
, override attemptAuthentication
as follows (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); }
Override supports
method in EmployeeCustomAuthenticationProvider
to support UsernamePasswordAuthenticationToken
.
Override supports
method in CustomerCustomAuthenticationProvider
to support CustomerAuthenticationToken
.
@Override public boolean supports(Class<?> authentication) { return (CustomerAuthenticationToken.class.isAssignableFrom(authentication)); }
Use both providers in authentication-manager
:
<security:authentication-manager alias="authenticationManager"> <security:authentication-provider ref='employeeCustomAuthenticationProvider ' /> <security:authentication-provider ref='customerCustomAuthenticationProvider ' /> </security:authentication-manager>
You can define several AuthenticationProcessingFilter
filters. Each of them can have different URL like /j_security_check_for_employee and /j_security_check_for_customer. Here is example of the security application context that demonstrates this idea:
<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>
As you can see, in this scenario you have also different UserDetailService
s - for DB auth and LDAP.
I think it's good idea to have different auth URLs for customers and employee (especially if they use different authentication strategies). You can even have different login pages for them.
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With