I'm using Spring Security 3.2.1.RELEASE with Spring MVC 4.0.4.RELEASE
I'm trying to setup Spring Security for a web application that will have two distinct login entry pages. I need the pages to be distinct as they will be styled and accessed differently.
First login page is for Admin users and protects admin pages /admin/**
Second login page is for Customer users and protects customer pages /customer/**.
I've attempted to setup two subclasses of WebSecurityConfigurerAdapter configuring individual HttpSecurity objects.
CustomerFormLoginWebSecurity is protecting customer pages and redirecting to customer login page if not authorised. The AdminFormLoginWebSecurity is protecting admin pages redirecting to admin login page if not authorised.
Unfortunately it seems that only the first of the configurations is enforced. I think that I am missing something extra to make these both work.
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Autowired
public void registerGlobalAuthentication(AuthenticationManagerBuilder auth) throws Exception {
auth
.inMemoryAuthentication()
.withUser("customer").password("password").roles("CUSTOMER").and()
.withUser("admin").password("password").roles("ADMIN");
}
@Configuration
@Order(1)
public static class CustomerFormLoginWebSecurity extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/customer/**").hasRole("CUSTOMER")
.and()
.formLogin()
.loginPage("/customer_signin")
.failureUrl("/customer_signin?error=1")
.defaultSuccessUrl("/customer/home")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.and()
.logout()
.permitAll();
http.exceptionHandling().accessDeniedPage("/customer_signin");
}
}
@Configuration
public static class AdminFormLoginWebSecurity extends WebSecurityConfigurerAdapter {
@Override
public void configure(WebSecurity web) throws Exception {
web
.ignoring()
.antMatchers("/", "/signin/**", "/error/**", "/templates/**", "/resources/**", "/webjars/**");
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.csrf().disable()
.authorizeRequests()
.antMatchers("/admin/**").hasRole("ADMIN")
.and()
.formLogin()
.loginPage("/admin_signin")
.failureUrl("/admin_signin?error=1")
.defaultSuccessUrl("/admin/home")
.loginProcessingUrl("/j_spring_security_check")
.usernameParameter("j_username").passwordParameter("j_password")
.and()
.logout()
.permitAll();
http.exceptionHandling().accessDeniedPage("/admin_signin");
}
}
}
The component of the spring login chain that redirects to a login page is the authentication filter, and the filter that get's plugged in when using
http.formLogin()
isDefaultLoginPageGeneratingFilter
.This filter either redirects to the login url or builds a default basic login page, if no login page url is provided.
What you need then is a custom authentication filter with the logic to define which login page is needed, and then plug it in the spring security chain in place of the single page authentication filter.
Consider creating a
TwoPageLoginAuthenticationFilter
by subclassingDefaultLoginPageGeneratingFilter
and overridinggetLoginPageUrl()
, and if that is not sufficient then copy the code and modify it to meet your needs.This filter is a
GenericFilterBean
, so you can declare it like this:then try building only one http configuration and don't set
formLogin()
, but instead do:and this will plug the two form authentication filter in the right place in the chain.
The solution that I have come to for multiple login pages involves a single http authentication but I provide my own implementations of
AuthenticationEntryPoint
AuthenticationFailureHandler
LogoutSuccessHandler
What I needed was for these implementations to be able to switch dependent on a token in the request path.
In my website the pages with a customer token in the url are protected and require a user to authenticate as CUSTOMER at the customer_signin page. So if wanted to goto a page /customer/home then I need to be redirected to the customer_signin page to authenticate first. If I fail to authenticate on customer_signin then I should be returned to the customer_signin with an error paramater. So that a message can be displayed.
When I am successfully authenticated as a CUSTOMER and then wish to logout then the LogoutSuccessHandler should take me back to the customer_signin page.
I have a similar requirement for admins needing to authenticate at the admin_signin page to access a page with an admin token in the url.
First I defined a class that would allow me to take a list of tokens (one for each type of login page)
I then use this in
PathLoginAuthenticationEntryPoint
to change the login url depending on the token in the request uri.PathTokenNotFoundException extends AuthenticationException so that you can handle it in the usual way.
Next I provide an implementation of
AuthenticationFailureHandler
that looks at the referer url in the request header to determine which login error page to direct the user to.Next I provide an implementation of
LogoutSuccessHandler
that will logout the user and redirect them to the correct signin page depending on the token in ther referer url in the request header.The final step is to wire them all together in the security configuration.
Once you have this configured you need a controller to to direct to the actual signin page. The SigninControiller below checks the queryString for a value that would indicate a signin error and then sets an attribute used to control an error message.
Maybe this post could help you : Multiple login forms
It's a different version of spring security but the same problem : only the first configuration is taken.
It seems it has been solved by changing login-processing-url for one of the two login pages but people suggest to use the same url processing but a different layout using ViewResolver. It is a solution if you use the same mechanism to authenticate users (the authentication mechanism is the thing responsible for processing the credentials that the browser is sending).
This post also seems to say that if you change your loginProcessingUrl you will succeed : Configuring Spring Security 3.x to have multiple entry points
I also encountered this problem and found out that I missed the first filtering part.
This one:
Should be:
Adding the first filtering .antMatcher("/admin/**") will first filter it so that it will use the AdminFormLoginWebSecurity instead of the other one.