Configuring Spring Security 3.x to have multiple entry points
Solution 1
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. ExtendAbstractAuthenticationToken
so that its class type is distinct fromUsernamePasswordAuthenticationToken
.-
Define a custom login filter:
<security:http> <security:custom-filter position="FORM_LOGIN_FILTER" ref="customFormLoginFilter" /> </security:http>
-
In
customFormLoginFilter
, overrideattemptAuthentication
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 inEmployeeCustomAuthenticationProvider
to supportUsernamePasswordAuthenticationToken
.-
Override
supports
method inCustomerCustomAuthenticationProvider
to supportCustomerAuthenticationToken
.@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>
Solution 2
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.
Solution 3
For Java Configuration reference
As i keen to write here java configuration way of implementing the same technique to help people who is not familiar with xml configuration but i don't want to hijack this thread beauty with such a long answer of java configuration code.
People who wants to achieve the same with java configuration(Annotation based) can refer my self answered question link is given below and also you can find my github repository link for the code in the answer.
For Annotation based configuration code refer my answer here Multiple AuthenticationProvider with different UsernamePasswordAuthToken to authenticate different login forms without fallback authentication
Comments
-
limc almost 2 years
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:-
- radio button field - for users to choose whether they are employees or customers.
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:-
- Employee form login submits to
/j_spring_security_check_for_employee
- Customer form login submits to
/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 thespringSecurityFilterChain
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...
- If employee logs in with right credential, they are allowed in... WORK AS EXPECTED.
- If employee logs in with wrong credential, they are not allowed in... WORK AS EXPECTED.
- If customer logs in with right credential, they are allowed in... WORK AS EXPECTED.
- If customer logs in with wrong credential, the authentication falls back to employee authentication... DOESN'T WORK. This is risky because if I select customer authentication, and punch it the employee credential, it will allow the user in too and this is not what I want.
<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 defaultUsernamePasswordAuthenticationToken
, I createdEmployeeUsernamePasswordAuthenticationToken
for it, just like the one I createdCustomerUsernamePasswordAuthenticationToken
forCustomerCustomAuthenticationProvider
. These providers will then override thesupports()
:-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.
-
limc over 13 yearsWhat if the employee user ID is the same as customer user ID? It might happen for this particular project. In this case, this employee will never get authenticated against LDAP then.
-
tenshi over 13 yearsSo you have the same DB record for the employee and customer? or all employee info is stored in LDAP?
-
limc over 13 yearsthe employees are stored in LDAP, but my concern is it is possible (in rare cases) that the customer user ID (ex: john01) matches the employee user ID (ex: john01) even though they represent two different persons.
-
tenshi over 13 yearsSeems that your requirements more constrained than I thought. I have another idea, but I added it as separate answer because it's very different from current one. Hope this helps...
-
limc over 13 yearsSorry for my late response, got caught up with something... thanks for the reply, by the way. One question I have is if you set up the filter-chain with authenticationProcessingFilterForCustomer then authenticationProcessingFilterForEmployee, if the customer authentication fails, wouldn't it falls back to the next filter which is the authenticationProcessingFilterForEmployee? Thanks.
-
tenshi over 13 yearsyes, it will, but since they have different URLs,
authenticationProcessingFilterForEmployee
will not be used and only one auth mechanism takes place. -
limc over 13 yearsI gave you +1 for being extremely patience with me here. :) For the filter chain, is it sufficient to specify just the only 2 authentication processing filters or do I need to specify all the filters listed at static.springsource.org/spring-security/site/docs/3.0.x/…? Another question I have is, how do I handle logout for both employee and customer? Do I need to create two logoutFilters too, and add them to the filter chain? Thanks much.
-
tenshi over 13 years1) no, in web.xml DelegatingFilterProxy just delegates all needed filtering to spring context (it normally should be declared only once and mapped to
/*
). 2) logout is easy - all you need to do - is invalidate the session like this:request.getSession().invalidate()
(it's totally independent from login type) -
limc over 13 yearsI commented out
springSecurityFilterChain
and add that 2 lines, and I get the following errors:Configuration problem: Filter beans '<authenticationProcessingFilterForEmployee>' and '<authenticationProcessingFilterForCustomer>' have the same 'order' value. When using custom filters, please make sure the positions do not conflict with default filters. Alternatively you can disable the default filters by removing the corresponding child elements from <http> and avoiding the use of <http auto-config='true'>.
-
limc over 13 yearsI set
auto-config
tofalse
and I still get that error. I think it is because both employee and customer useUsernamePasswordAuthenticationFilter
. I tried creating my custom filter that extends that filter, and I get the same error too. -
tenshi over 13 yearscan you change first tag to
<sec:custom-filter ref="authenticationProcessingFilterForCustomer" position="FIRST"/>
(position
instead ofafter
) -
limc over 13 yearsThanks for your response. Does the
customFormLoginFilter
extendUsernamePasswordAuthenticationFilter
? I'm afraid if that filter implementsUsernamePasswordAuthenticationFilter
, I will get the "two filters in the same order" exception as described in my response to @EasyAngel. Can you explain why #6 needs to overridesupport()
but #5 doesn't need to? -
limc over 13 yearsI'm giving you +1 here for pointing me to the right direction. I have one problem with your approach regarding fall back authentication. I have updated my post above with your name on it.
-
tenshi over 13 yearshmmm... it's strange... I looked at correspondent
BeanDefinitionParser
and found out that order of the filter is assigned there. According to the XSD (springframework.org/schema/security/spring-security-3.1.xsd) you can place filters inposition
,before
,or after
some named filter (possible values can be found in simpleTypenamed-security-filter
). In order to make it work you can try to place these 2 filter in (or before/after) different named filters (I placed them bothFIRST
position, but seems that it's wrong). -
tenshi over 13 yearsYou can also look at
org.springframework.security.config.http.HttpSecurityBeanDefinitionParser
in methodbuildCustomFilterList
- may be you will find solution for the problem there. I also can recommend to try to debug application during sturtup and try to find out why both filters receive the same order even if you placed them in different slots. (I even don't remember how many hours I spent in past debugging spring-security, but it was very helpful. I learned a lot about it) -
limc over 13 yearsI have solved the problem. Anyhow, I'll go ahead and award you with +50 bounty and +1 and a check mark, that's the max possible points I can award you for helping me out. Thank you so much.
-
We are Borg over 9 yearsHello, I am using ur method in a similar manner, but I am having type conversion errors, can you please check this post : stackoverflow.com/questions/28041310/…
-
PraveenKumar Lalasangi over 4 yearsIt is my favorite thread as it helped me in understanding spring-security, custom filters, fallback authentication, custom authentication providers and custom authentication tokens. Special thanks to each contributors of this thread.