1. Introduction
In this tutorial, we will see how we can configure Spring Security to work with two different login pages using two different Spring Security http elements in the configuration.
2. Configuring 2 Http Elements
One of the situations in which we may need two login pages is when we have one page for administrators of an application and a different page for normal users.
We will configure two http elements that will be differentiated by the URL pattern associated with each:
- /user* for pages that will need a normal user authentication to be accessed
- /admin* for pages that will be accessed by an administrator
Each http element will have a different login page and a different login processing URL.
In order to configure two different http elements, let’s create two static classes annotated with @Configuration that extend the WebSecurityConfigurerAdapter.
Both will be placed inside a regular @Configuration class:
@Configuration @EnableWebSecurity public class SecurityConfig { ... }
Let’s define the WebSecurityConfigurerAdapter for the “ADMIN” users:
@Configuration @Order(1) public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter { public App1ConfigurationAdapter() { super(); } @Override protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/admin*") .authorizeRequests() .anyRequest() .hasRole("ADMIN") .and() .formLogin() .loginPage("/loginAdmin") .loginProcessingUrl("/admin_login") .failureUrl("/loginAdmin?error=loginError") .defaultSuccessUrl("/adminPage") .and() .logout() .logoutUrl("/admin_logout") .logoutSuccessUrl("/protectedLinks") .deleteCookies("JSESSIONID") .and() .exceptionHandling() .accessDeniedPage("/403") .and() .csrf().disable(); } }
And now, let’s define the WebSecurityConfigurerAdapter for normal users:
@Configuration @Order(2) public static class App2ConfigurationAdapter extends WebSecurityConfigurerAdapter { public App2ConfigurationAdapter() { super(); } protected void configure(HttpSecurity http) throws Exception { http.antMatcher("/user*") .authorizeRequests() .anyRequest() .hasRole("USER") .and() .formLogin() .loginPage("/loginUser") .loginProcessingUrl("/user_login") .failureUrl("/loginUser?error=loginError") .defaultSuccessUrl("/userPage") .and() .logout() .logoutUrl("/user_logout") .logoutSuccessUrl("/protectedLinks") .deleteCookies("JSESSIONID") .and() .exceptionHandling() .accessDeniedPage("/403") .and() .csrf().disable(); } }
Note that by placing the @Order annotation on each static class, we are specifying the order in which the two classes will be considered based on the pattern matching when a URL is requested.
Two configuration classes cannot have the same order.
3. Custom Login Pages
We will create our own custom login pages for each type of user. For the administrator user, the login form will have a “user_login” action, as defined in the configuration:
<p>User login page</p> <form name="f" action="user_login" method="POST"> <table> <tr> <td>User:</td> <td><input type="text" name="username" value=""></td> </tr> <tr> <td>Password:</td> <td><input type="password" name="password" /></td> </tr> <tr> <td><input name="submit" type="submit" value="submit" /></td> </tr> </table> </form>
The administrator login page is similar, except the form will have an action of “admin_login” as per the java configuration.
4. Authentication Configuration
Now we need to configure authentication for our application. Let’s look at two ways to accomplish this — one using a common source for user authentication, and the other using two separate sources.
4.1. Using a Common User Authentication Source
If both login pages share a common source for authenticating users, you can create a single bean of type UserDetailsService that will handle the authentication.
Let’s demonstrate this scenario using an InMemoryUserDetailsManager that defines two users — one with a role of “USER” and one with a role of “ADMIN”:
@Bean public UserDetailsService userDetailsService() throws Exception { InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager(); manager.createUser(User .withUsername("user") .password("userPass") .roles("USER") .build()); manager.createUser(User .withUsername("admin") .password("adminPass") .roles("ADMIN") .build()); return manager; }
4.2. Using Two Different User Authentication Sources
If you have different sources for user authentication — one for administrators and one for normal users — you can configure an AuthenticationManagerBuilder inside each static @Configuration class. Let’s look at an example of an authentication manager for an “ADMIN” user:
@Configuration @Order(1) public static class App1ConfigurationAdapter extends WebSecurityConfigurerAdapter { @Override protected void configure(AuthenticationManagerBuilder auth) throws Exception { auth.inMemoryAuthentication() .withUser("admin") .password("admin") .roles("ADMIN"); } }
In this case, the UserDetailsService bean from the previous section will no longer be used.
6. Conclusion
In this quick tutorial, we’ve shown how to implement two different login pages in the same Spring Security application.
The complete code for this article can be found in the GitHub project.
When you run the application, you can access the examples above on the /protectedLinks URI.