Spring Security Role Hierarchy issues - spring

I am trying to enable role hierarchy voting in Spring Security when authenticating using Waffle NTML but having some unknown issues in that the inherited roles do not appear as authorities on the principal as expected preventing hasRole expressions in both the intercept urls and using the authorize jsp taglibs.
I have been integrating waffle based on the following guide: https://github.com/dblock/waffle/blob/master/Docs/spring/SpringSecuritySingleSignOnFilter.md
This works within the application as expected on its own using the standard RoleVoter but the problem starts when I try to customise it to use the RoleHierarchyVoter which I have also tested on its own (using an LDAP Authentication Provider) and the role hierarchies work exactly as expected.
The config for the combined Waffle and RoleHierarchyVoter approach is as follows:
Waffle Specfic Config
<!-- windows authentication provider -->
<bean id="waffleWindowsAuthProvider" class="waffle.windows.auth.impl.WindowsAuthProviderImpl" />
<!-- collection of security filters -->
<bean id="negotiateSecurityFilterProvider" class="waffle.servlet.spi.NegotiateSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
</bean>
<bean id="basicSecurityFilterProvider" class="waffle.servlet.spi.BasicSecurityFilterProvider">
<constructor-arg ref="waffleWindowsAuthProvider" />
</bean>
<bean id="waffleSecurityFilterProviderCollection" class="waffle.servlet.spi.SecurityFilterProviderCollection">
<constructor-arg>
<list>
<ref bean="negotiateSecurityFilterProvider" />
<ref bean="basicSecurityFilterProvider" />
</list>
</constructor-arg>
</bean>
<bean id="negotiateSecurityFilterEntryPoint" class="waffle.spring.NegotiateSecurityFilterEntryPoint">
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
</bean>
<!-- spring security filter -->
<bean id="waffleNegotiateSecurityFilter" class="waffle.spring.NegotiateSecurityFilter">
<property name="Provider" ref="waffleSecurityFilterProviderCollection" />
<property name="AllowGuestLogin" value="false" />
<property name="PrincipalFormat" value="fqn" />
<property name="RoleFormat" value="fqn" />
<property name="GrantedAuthorityFactory" ref="simpleGrantedAuthorityFactory" />
<!-- set the default granted authority to null as we don't need to assign a default role of ROLE_USER -->
<property name="defaultGrantedAuthority"><null/></property>
</bean>
<!-- custom granted authority factory so the roles created are based on the name rather than the fqn-->
<bean id="simpleGrantedAuthorityFactory" class="xx.yy.zz.SimpleGrantedAuthorityFactory">
<constructor-arg name="prefix" value="ROLE_"/>
<constructor-arg name="convertToUpperCase" value="true"/>
</bean>
Familiar Spring Security Config
<!-- declare the entry point ref as the waffle defined entry point -->
<sec:http use-expressions="true"
disable-url-rewriting="true"
access-decision-manager-ref="accessDecisionManager"
entry-point-ref="negotiateSecurityFilterEntryPoint" >
<sec:intercept-url pattern="/**" access="isAuthenticated()" requires-channel="any"/>
.
. access denied handlers, concurrency control, port mappings etc
.
<sec:custom-filter ref="waffleNegotiateSecurityFilter" position="BASIC_AUTH_FILTER" />
</sec:http>
<!-- spring authentication provider -->
<sec:authentication-manager alias="authenticationProvider" />
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<ref bean="roleHierarchyVoter" />
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter">
<property name="expressionHandler">
<bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy"/>
</bean>
</property>
</bean>
</list>
</property>
</bean>
<bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_TEST_1 > ROLE_TEST_2
ROLE_TEST_2 > ROLE_TEST_3
ROLE_TEST_3 > ROLE_TEST_4
</value>
</property>
</bean>
<bean id="roleHierarchyVoter"
class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy"/>
</bean>

Managed to fix my issues which was down to an omission in my http namespace configuration which I found from hours of debugging the spring security source.
The issue was how the DefaultWebSecurityExpressionHandler was created. In the snipped above it had created it as inner bean inside the bean definition of the accessDecisionManager:
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter">
<property name="expressionHandler">
<bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy"/>
</bean>
</property>
</bean>
With this the role heirachies are used to determine whether access should be granted when processing rules defined as intercept urls such as:
<sec:intercept-url pattern="/**" access="isAuthenticated()" requires-channel="any"/>
But if you want to check authorisation using the JSP Authorize taglib as below (this is in freemarker) it will not work as the roleHeirachies do not get taken into account:
<#security.authorize access="hasRole('ROLE_TEST_1)">
<p>You have role 1</p>
</#security.authorize>
<#security.authorize access="hasRole('ROLE_TEST_4')">
<p>You have role 4</p>
</#security.authorize>
This is because the DefaultWebSecurityExpressionHandler created as an inner bean is only used within the access decision manager but for taglib expressions a NEW default bean will be created (which doesn't use the RoleHierarchy) unless an security http namespace expression-handler is defined.
So, to resolve my issues I created the bean DefaultWebSecurityExpressionHandler and referenced it within my WebExpressionVoter bean definition and also used it as the expression handler as follows:
<sec:http ... >
.
. access denied handlers, concurrency control, port mappings etc
.
<sec:expression-handler ref="defaultWebSecurityExpressionHandler" />
</sec:http>
<bean id="defaultWebSecurityExpressionHandler"
class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy"/>
</bean>
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<ref bean="roleHierarchyVoter" />
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter">
<property name="expressionHandler" ref="defaultWebSecurityExpressionHandler"/>
</bean>
</list>
</property>
</bean>
Making these changes ensures the roleHeirarchies are taken into account for both Web Security Expressions defined as intercept URLs via the http namespace and also expressions using the JSP Authorize taglib.

Related

Spring facebook login: signup redirect

SCENARIO 1
When logging in with facebook to my webapp
If the browser is already logged in to Facebook, and
if the user's social authentication details are already registered in my webapp,
then when the user clicks "Sign in with Facebook" on my webapp,
he/she is authorized by Facebook and
the Authorization object returned by
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
is the named user.
All is good.
SCENARIO 2
If the browser is NOT logged into Facebook but
the user's social authentication details are already registered in my webapp,
then when the user clicks "Sign in with Facebook" on my webapp,
the user signs in with Facebook by entering username and password BUT
the the Authorization object returned by
Authentication auth = SecurityContextHolder.getContext().getAuthentication();
is authorized as anonymous and has the name anonymousUser.
Moreover, the call from Facebook after authorization is to /signup
However, I am expecting the same registered user as in scenario 1
My security configuration is below. Can anyone give me a hint as to what the problem may be?
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:security="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">
<security:http use-expressions="true" entry-point-ref="appAuthenticationEntryPoint">
<security:intercept-url pattern="/login" access="permitAll()" />
<security:intercept-url pattern="/flow-entry.html" access="permitAll()"/>
<security:intercept-url pattern="/flow-jobpostdata.html" access="permitAll()"/>
<security:intercept-url pattern="/flow-jobpostdata_anydegree.html" access="permitAll()"/>
<security:intercept-url pattern="/j_spring_security_check" access="permitAll()"/>
<!-- Adds social authentication filter to the Spring Security filter chain. -->
<security:custom-filter before="PRE_AUTH_FILTER" ref="socialAuthenticationFilter"/>
<security:custom-filter position="FORM_LOGIN_FILTER" ref="SecurityAuthFilter"/>
</security:http>
<!-- authentication manager and its provider( social provider deals with social login & local user provider deals with form login ) -->
<security:authentication-manager alias="authenticationManager">
<security:authentication-provider ref="socialAuthenticationProvider"/>
<security:authentication-provider user-service-ref="localUserDetailService"/>
</security:authentication-manager>
<bean id="customAuthenticationProvider" class="com.ikoda.service.loginservices.CustomAuthenticationProvider">
<property name="auService" ref="auService" />
</bean>
<bean id="socialAuthenticationProvider" class="org.springframework.social.security.SocialAuthenticationProvider">
<constructor-arg ref="inMemoryUsersConnectionRepository"/>
<constructor-arg ref="socialUserDetailService"/>
</bean>
<!-- form login beans -->
<bean id="successHandler" class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
</bean>
<bean id="appAuthenticationEntryPoint"
class="com.ikoda.service.loginservices.AppAuthenticationEntryPoint">
<constructor-arg name="loginFormUrl" value="/login"/>
</bean>
<bean id="rememberMeServices"
class="org.springframework.security.web.authentication.NullRememberMeServices"/>
<bean id="failureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<constructor-arg name="defaultFailureUrl" value="/login?error=true"/>
</bean>
<bean class="org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter"
id="SecurityAuthFilter">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="authenticationSuccessHandler" ref="successHandler"/>
<property name="authenticationFailureHandler" ref="failureHandler"/>
<property name="filterProcessesUrl" value="/j_spring_security_check"/>
<property name="rememberMeServices" ref="rememberMeServices"/>
</bean>
<!-- social login filter which is a pre authentication filter and works for /auth service url -->
<bean id="socialAuthenticationFilter" class="org.springframework.social.security.SocialAuthenticationFilter">
<constructor-arg name="authManager" ref="authenticationManager"/>
<constructor-arg name="userIdSource" ref="userIdSource"/>
<constructor-arg name="usersConnectionRepository" ref="inMemoryUsersConnectionRepository"/>
<constructor-arg name="authServiceLocator" ref="appSocialAuthenticationServiceRegistry"/>
<property name="authenticationSuccessHandler" ref="successHandler"/>
</bean>
<!-- inmemory connection repository which holds connection repository per local user -->
<bean id="inMemoryUsersConnectionRepository"
class="org.springframework.social.connect.mem.InMemoryUsersConnectionRepository">
<constructor-arg name="connectionFactoryLocator" ref="appSocialAuthenticationServiceRegistry"/>
<property name="connectionSignUp" ref="connectionSignUp"/>
</bean>
<!-- service registry will holds connection factory of each social provider-->
<bean id="appSocialAuthenticationServiceRegistry"
class="com.ikoda.service.loginservices.AppSocialAuthenticationServiceRegistry">
<constructor-arg>
<list>
<ref bean="facebookAuthenticationService"/>
<ref bean="googleAuthenticationService"/>
</list>
</constructor-arg>
</bean>
<bean id="facebookAuthenticationService"
class="org.springframework.social.facebook.security.FacebookAuthenticationService">
<constructor-arg name="apiKey" value="11111"/>
<constructor-arg name="appSecret" value="11111"/>
</bean>
<bean id="googleAuthenticationService"
class="org.springframework.social.google.security.GoogleAuthenticationService">
<constructor-arg name="apiKey" value="111-lpmhcmuj1577bd6god0696g4u2g16c1i.apps.googleusercontent.com"/>
<constructor-arg name="appSecret" value="111-111-"/>
</bean>
<bean id="userIdSource" class="org.springframework.social.security.AuthenticationNameUserIdSource"/>
<!-- If no local user is associated to a social connection then connection sign up will create a new local user and map it to social user -->
<bean id="connectionSignUp" class="com.ikoda.service.loginservices.AppConnectionSignUp"/>
</beans>
The redirect to signUp is the intended behaviour of Spring Social. The key is how to manage the behaviour.
One way to solve the problem is by authenticating the social login in the controller signup method. To do this requires accessing the social connection. You can do this by autowiring ProviderSignInUtils in your controller.
Note AppConnectionSignUP is some implementation of org.springframework.social.connect.ConnectionSignUp
Connection<?> connection = providerSignInUtils.getConnectionFromSession(webRequest);
if (null != connection)
{
String userIdString = appConnectionSignUp.execute(connection);
}
Another way to solve the problem is to implement your own ConnectionSignUp
<bean id="connectionSignUp" class="com.ikoda.service.loginservices.AppConnectionSignUp"/>
and reference that in your UsersConnectionRepository
<bean id="jdbcUsersConnectionRepository"
class="org.springframework.social.connect.jdbc.JdbcUsersConnectionRepository">
<constructor-arg ref="dataSource" />
<constructor-arg ref="appSocialAuthenticationServiceRegistry" />
<constructor-arg ref="textEncryptor" />
<property name="connectionSignUp" ref="connectionSignUp"/>
</bean>
If you take this approach, then social login will bypass the Controller /signUp and directly invoke the execute method of connectionSignUP bean.
Meanwhile, in the question above the inMemoryUsersConnectionRepository was ill-advised. I switched to the jdbcUsersConnectionRepository.

What is the URL for embedded LDAP server

I need to use an Embedded server
<security:authentication-manager>
<security:ldap-authentication-provider
user-search-filter="(uid={0})"
user-search-base="ou=users"
group-search-filter="(uniqueMember={0})"
group-search-base="ou=groups"
group-role-attribute="cn"
role-prefix="ROLE_">
</security:ldap-authentication-provider>
</security:authentication-manager>
<security:ldap-server ldif="classpath:mojo_working.ldif" root="dc=example,dc=com" />
for custom populator.
its like
<bean id="ldapAuthProvider" class="org.springframework.security.ldap.authentication.LdapAuthenticationProvider">
<constructor-arg ref="authenticator"/>
<constructor-arg ref="populator"/>
</bean>
<bean id="authenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="contextSource"/>
<property name="userDnPatterns">
<list>
<value>uid={0},ou=users</value>
</list>
</property>
</bean>
In this case what can be contextSource for Embedded LDAP Server.
The ldap-server element creates a ContextSource so you don't need to define one. It supports an id attribute, which you can use to create a reference to the bean.
<security:ldap-server id="embeddedServer" ... />
<bean id="authenticator" class="org.springframework.security.ldap.authentication.BindAuthenticator">
<constructor-arg ref="embeddedServer"/>
...
</bean>
So normally you don't need the URL. It also has a port element (default 33389), which you can set. The URL would be ldap://localhost:33389/dc=example,dc=com unless you set a different port.

AOP MethodSecurityInterceptor not getting invoked in Spring Security

I am using Spring Security 3.0.7 and my application is deployed on JBOSS.
I am setting up org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor for my application to add restrictions on invocation of certain methods in services layer. But for some reason the interceptor is not getting called and I am able to invoke all the methods from a user with a role, ROLE_USER.
My security.xml looks like this:
<security:http auto-config='true' authentication-manager-ref="authenticationManager" use-expressions="true" request-matcher="ant" create-session="always"
entry-point-ref="authenticationEntryPoint" >
<security:intercept-url pattern="/login.jsp" access="permitAll" />
<security:intercept-url pattern="/configure/" access="hasRole('ROLE_ADMIN')" />
<security:intercept-url pattern="/**" access="isAuthenticated()" />
<security:form-login login-page="/login.jsp" authentication-failure-url="/login.jsp?error=Authentication Failed!" default-target-url="/landing.do"
always-use-default-target="true" />
<security:logout invalidate-session="true" delete-cookies="true" />
<security:session-management>
<security:concurrency-control max-sessions="1" error-if-maximum-exceeded="true" expired-url="/login.jsp"/>
</security:session-management>
</security:http>
<security:method-security-metadata-source id="securityMetadataSource">
<security:protect method="com.services.CreateUserService.createUser" access="ROLE_ADMIN"/>
<security:protect method="com.services.DeleteUser.deleteUser" access="ROLE_ADMIN"/>
</security:method-security-metadata-source>
<security:global-method-security authentication-manager-ref="authenticationManager" access-decision-manager-ref="accessDecisionManager"
metadata-source-ref="securityMetadataSource" pre-post-annotations="disabled" secured-annotations="disabled" />
<bean id="authenticationEntryPoint" class="org.springframework.security.web.authentication.LoginUrlAuthenticationEntryPoint">
<property name="loginFormUrl" value="/login.jsp"/>
</bean>
<bean id="myRoleVoter" class="com.interceptors.MyRoleVoter" />
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager" >
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
</list>
</property>
</bean>
<bean id="accessDecisionManager" class="com.interceptors.MyAccessDecisionManager" p:allowIfAllAbstainDecisions="false" >
<property name="decisionVoters">
<list>
<ref local="myRoleVoter"/>
</list>
</property>
</bean>
<bean id="methodSecurity" class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="securityMetadataSource" ref="securityMetadataSource" />
</bean>
The authentication part is working absolutely fine. However, my MethodSecurityInterceptor is never invoked and hence not even my AccessDecisionManager or RoleVoter.
If I add the reference of my accessDecisionManager in the first line, then my authentication layer stops working. All the requests pass to the AccessDecisionManager with the user as anonymous.
<security:http security="none" pattern="/login.jsp" />
<security:http auto-config='true' authentication-manager-ref="authenticationManager" access-decision-manager-ref="accessDecisionManager" use-expressions="true" request-matcher="ant" create-session="always"
entry-point-ref="authenticationEntryPoint" >
I know either I am missing some TINY configuration, but I am unable to find that configuration anywhere in the docs.
Possibly, you have declared your MethodSecurityInterceptor in your application root context, and expect it to work for the beans in the servlet context.
If you want global-method-security to work in the servlet context, you should declare it explicitly in the servlet xml configuration.
It's a very common mistake to declare AOP interceptors in the wrong context, so please check that your service layer beans are not being created(for example by autoscanning) in the servlet context.
I have figured out the solution. I have to define two authentication manager and one access decision manager. One authentication manager goes in my root context and would be used by the global method security to create the form login authentication mechanism.
The other authentication manager and the access decision manager goes in services context, where it would be used to create my custom method security interceptor.
applicationContext-Security.xml
<!-- The authentication manager responsible for authenticating the users -->
<bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager" >
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
</list>
</property>
</bean>
The context file for the services.
service.security.xml
<!--we need a separate authentication manager for method security -->
<bean id="methodAuthenticationManager" class="org.springframework.security.authentication.ProviderManager" >
<property name="providers">
<list>
<ref local="daoAuthenticationProvider"/>
</list>
</property>
</bean>
<!--we need a separate accessDecisionManager for method security -->
<bean id="methodAccessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased" >
<property name="decisionVoters">
<list>
<ref local="myRoleVoter"/> <!-- the voter will decide weather methodInvocation is allowed or not -->
</list>
</property>
</bean>
<!-- The Method Security Interceptor to intercept all the calls to methods -->
<bean id="methodSecurityInterceptor" class="org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="methodAuthenticationManager"/>
<property name="accessDecisionManager" ref="methodAccessDecisionManager"/>
<property name="securityMetadataSource" ref="swiftSecurityMethodMetadataSource" />
</bean>
<security:method-security-metadata-source id="securityMetadataSource">
<security:protect method="fullyQualifiedMethod" access="Administrator"/>
</security:method-security-metadata-source>
<security:global-method-security authentication-manager-ref="methodAuthenticationManager" access-decision-manager-ref="methodAccessDecisionManager"
metadata-source-ref="swiftSecurityMethodMetadataSource" jsr250-annotations="disabled" secured-annotations="disabled"/>
Another usual reason why AOP MethodSecurityInterceptor is not invoked - you miss cglib or similar for byte code manipulation.
So only java.lang.reflect.Proxy used and thus it is not possible to proxy private methods! Just make #Secured methods public - and all be fine!

AccessDeniedException if using RoleHierarchyImpl

I am using role hierarchy in Spring Security.
<beans:bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
<beans:constructor-arg ref="roleHierarchy" />
</beans:bean>
<beans:bean id="roleHierarchy"
class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<beans:property name="hierarchy">
<beans:value>
ROLE_USER > ROLE_GUEST
</beans:value>
</beans:property>
</beans:bean>
I am securing methods using protect-pointcut
<global-method-security secured-annotations="enabled" pre-post-annotations="enabled">
<protect-pointcut expression="execution(* my.package.*(..))"
access="ROLE_GUEST"/>
</global-method-security>
However, I got AccessDeniedException if I login with user that has authority ROLE_USER. I have no issue if I specified protect-pointcut with access="ROLE_GUEST,ROLE_USER".
Am I missing some steps? FYI, I am using Spring 3.0.5.
Thanks.
Don't forget to add a WebExpressionVoter to be able to also use expressions in http element:
<sec:http use-expressions="true" access-decision-manager-ref="accessDecisionManager">
<sec:intercept-url pattern="/index.html" access="hasRole('ROLE_AUTHENTICATED')" />
<sec:intercept-url pattern="/admin" access="hasRole('ROLE_SUPERVISOR')" />
...
So I end up with an accessDecisionManager containing a role hierarchy voter and a WebExpressionVoter, both using the same roleHierarchyImpl bean.
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<ref bean="roleHierarchyVoter" />
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter">
<property name="expressionHandler">
<bean class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy"/>
</bean>
</property>
</bean>
<bean class="org.springframework.security.access.vote.AuthenticatedVoter"/>
</list>
</property>
</bean>
<bean id="roleHierarchyVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy" />
</bean>
<bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_SUPERVISOR > ROLE_XX
ROLE_XX > ROLE_AUTHENTICATED
ROLE_AUTHENTICATED > ROLE_UNAUTHENTICATED
</value>
</property>
</bean>
(spring sec 3.1)
The nested beans are slightly wrong in jgraglia example above, and you don't need <ref bean="roleHierarchyVoter" /> because the hierarchy is handled in WebExpressionVoter. I'm doing this in Spring Security 4.0.0, but the code looks the same except you don't need use-expressions="true" because it's on by default.
I usually try and nest my beans as much as possible, so my code has no ref="" values unless required.
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<constructor-arg>
<bean class="org.springframework.security.web.access.expression.WebExpressionVoter">
<property name="expressionHandler" ref="webExpressionHandler" />
</bean>
</constructor-arg>
</bean>
<bean id="webExpressionHandler" class="org.springframework.security.web.access.expression.DefaultWebSecurityExpressionHandler">
<property name="roleHierarchy" ref="roleHierarchy"/>
</bean>
<bean id="roleHierarchy" class="org.springframework.security.access.hierarchicalroles.RoleHierarchyImpl">
<property name="hierarchy">
<value>
ROLE_ADMIN > ROLE_USER
ROLE_USER > ROLE_ANONYMOUS
</value>
</property>
</bean>
Have a look at bug report SEC-1163 and the comment below.
If you want basic support for role hierarchies, then use a RoleHierarchyVoter, instead of a RoleVoter.
So you need somethink like:
<bean id="accessDecisionManager" class="org.springframework.security.access.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<ref bean="roleHierarchyVoter" />
<ref bean="authenticatedVoter" />
<ref bean="preAdviceVoter" />
<ref bean="mediaItemReadVoter" />
<ref bean="mediaItemWriteVoter" />
</list>
</property>
</bean>
<bean id="roleVoter" class="org.springframework.security.access.vote.RoleHierarchyVoter">
<constructor-arg ref="roleHierarchy"/>
</bean>

Spring 3.x configuration for multiple login pages

I'm using Spring 3.1 for authentication purpose.
My requirement:
Two different login pages. One for Customer and other for Employee.
Each after successful authentication, will be forwarded to respective successful URL.
My spring security configuration:
<sec:http pattern="/resources/**" security="none" />
<sec:http auto-config="true">
<sec:intercept-url pattern="/**" access="IS_AUTHENTICATED_ANONYMOUSLY" />
<sec:intercept-url pattern="/customer/**" access="ROLE_CUSTOMER" />
<sec:intercept-url pattern="/employee/**" access="ROLE_EMPLOYEE" />
</sec:http>
<bean id="springSecurityFilterChain" class="org.springframework.security.web.FilterChainProxy">
<sec:filter-chain-map path-type="ant">
<sec:filter-chain pattern="/**"
filters="authenticationProcessingFilterForCustomer,authenticationProcessingFilterForEmployee" />
</sec:filter-chain-map>
</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" />
<property name="authenticationSuccessHandler" ref="customerSuccessHandler" />
<property name="authenticationFailureHandler" ref="customerFailureHandler" />
</bean>
<bean id="customerSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/customer/index.html" />
</bean>
<bean id="customerFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/customer.html?login_error=1" />
</bean>
<bean id="authenticationManagerForCustomer"
class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref bean="customCustomerAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="customCustomerAuthenticationProvider" class="com.edu.CustomerCustomAuthenticationProvider">
<property name="userDetailsService">
<bean class="com.edu.CustomerUserDetailsService" />
</property>
</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" />
<property name="authenticationSuccessHandler" ref="employeeSuccessHandler" />
<property name="authenticationFailureHandler" ref="employeeFailureHandler" />
</bean>
<bean id="employeeSuccessHandler"
class="org.springframework.security.web.authentication.SavedRequestAwareAuthenticationSuccessHandler">
<property name="defaultTargetUrl" value="/employee/index.html" />
</bean>
<bean id="employeeFailureHandler"
class="org.springframework.security.web.authentication.SimpleUrlAuthenticationFailureHandler">
<property name="defaultFailureUrl" value="/employee.html?login_error=1" />
</bean>
<bean id="authenticationManagerForEmployee"
class="org.springframework.security.authentication.ProviderManager">
<property name="providers">
<list>
<ref bean="customEmployeeAuthenticationProvider" />
</list>
</property>
</bean>
<bean id="customEmployeeAuthenticationProvider" class="com.edu.EmployeeCustomAuthenticationProvider">
<property name="userDetailsService">
<bean class="com.edu.EmployeeUserDetailsService" />
</property>
</bean>
<sec:authentication-manager alias="authenticationManager">
<sec:authentication-provider ref="customCustomerAuthenticationProvider" />
<sec:authentication-provider ref="customEmployeeAuthenticationProvider" />
</sec:authentication-manager>
Both CustomAuthenticationProvider have implemented Support method as follows:
public boolean supports(Class<? extends Object> authentication) {
return UsernamePasswordAuthenticationToken.class.isAssignableFrom(authentication);
}
After launching application, while trying to authenticate, the message displayed in login pages are:
Your login attempt was not successful, try again.
Reason: No AuthenticationProvider found for org.springframework.security.authentication.UsernamePasswordAuthenticationToken
I'm using Spring 3.1. Any help appreciated.
Thank You
I have done similar things in grails, what you need is:
extend UsernamePasswordAuthenticationToken, create two sub-class for employee and customer, say EmployeeUsernamePasswordAuthenticationToken and CustomerUsernamePasswordAuthenticationToken
extend UsernamePasswordAuthenticationFilter, to create different instance of EmployeeUsernamePasswordAuthenticationToken or CustomerUsernamePasswordAuthenticationToken based on current auth request
extend AuthenticationProvider for employee and custoner, create two class say EmployeeAuthenticationProvider and CustomerAuthenticationProvider, overwrite each class' supports method to support its target UsernamePasswordAuthenticationToken
you only need one authenticationManager, register both provide into it
only need one AuthenticationSuccessHandler, you can decide which url want to go in it
I also create a my own instance of AuthenticationEntryPoint to support multi entrypoint
Beginning from Spring 3.1 you have as many configuration as you want :
https://jira.springsource.org/browse/SEC-1171
You should point the authenticationManager ref in 'authenticationProcessingFilterForCustomer' and 'authenticationProcessingFilterForEmployee' beans to correct bean i.e. 'authenticationManager' which has providers. No need to define 'authenticationManagerForCustomer' and 'authenticationManagerForEmployee' beans.

Resources