The HttpSecurity.formLogin DSL only supports a single log in URL because that is what is most common. However, you can do this by explicitly registering a second UsernamePasswordAuthenticationFilter. The documentation has some nice diagrams of how form based log in works.
I created a sample (make sure to use the linked branch). Below is a summary and description of what is happening:
@Configuration
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// allow anyone to access the admin log in page
.mvcMatchers("/admin_login").permitAll()
// require admin access to any admin URLs
.mvcMatchers("/admin/**").hasRole("ADMIN")
// any other URL just requires to be authenticated
.anyRequest().authenticated()
.and()
// configure the user based authentication
.formLogin()
// this means you should serve a log in page for users at GET /user_login
// Spring Security will process POST /user_login as a user log in
.loginPage("/user_login")
// allow anyone to access the /user_login since they aren't authenticated when they see a log in page
.permitAll()
.and()
// formLogin above only supports a single repository because that is what is most common
// fortunately formLogin is just a shortcut for the code below
// here we add the admin login form explicitly
.addFilter(adminAuthenticationFilter());
}
// formLogin for users will pick up a UserDetailsService exposed as a Bean
@Bean
static InMemoryUserDetailsManager userDetailsManager() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("user")
.roles("USER")
.build();
return new InMemoryUserDetailsManager(user);
}
// create an admin version of UsernamePasswordAuthenticationFilter
private static UsernamePasswordAuthenticationFilter adminAuthenticationFilter() {
// inject the adminAuthenticationProvider so only admins are authenticated with this Filter
UsernamePasswordAuthenticationFilter result = new UsernamePasswordAuthenticationFilter(adminAuthenticationProvider());
// only process POST /admin_login
result.setRequiresAuthenticationRequestMatcher(new AntPathRequestMatcher("/admin_login", "POST"));
// errors should go to /admin_login?error
result.setAuthenticationFailureHandler(new SimpleUrlAuthenticationFailureHandler("/admin_login?error"));
return result;
}
// create an AuthenticationManager that is only used by Admin users
private static AuthenticationManager adminAuthenticationProvider() {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(adminUsers());
return new ProviderManager(authenticationProvider);
}
// we use the same username as user based to demon that it will work properly
// the difference is that the password is admin and the user will have admin role so it can access URLs in /admin/
static InMemoryUserDetailsManager adminUsers() {
UserDetails user = User.withDefaultPasswordEncoder()
.username("user")
.password("admin")
.roles("USER", "ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
}
As mentioned above you are responsible for creating the log in pages and ensuring they post to the correct URLs. The first step is to create a controller that maps the URLs to the views you want to display. Here we use a single Controller for convenience, but you can split this up:
@Controller
public class LoginController {
@GetMapping("/admin_login")
String adminLogin() {
return "admin_login";
}
@GetMapping("/user_login")
String login() {
return "user_login";
}
}
Then you need to have two views. The first view is admin_login.html. In a Boot + Thymeleaf application something like this would be located in src/main/resources/templates/user_login.html
<!DOCTYPE html>
<html lang="en" xmlns:th="http://www.thymeleaf.org">
<head>
<title>User Log In</title>
</head>
<body>
<div class="container">
<h1>User Log In</h1>
<form method="post"
th:action="@{/user_login}">
<div th:if="${param.error}">
Invalid username and password.
</div>
<div th:if="${param.logout}">
You have been logged out.
</div>
<div>
<label for="username">Username</label>
<input type="text"
id="username"
name="username"
placeholder="Username"
required autofocus>
</div>
<div class="form-group">
<label for="password">Password</label>
<input type="password"
id="password"
name="password"
placeholder="Password"
required>
</div>
<button type="submit">Sign in</button>
</form>
</div>
</body>
</html>
This is all detailed in the link I provided above. The key is that it submits a POST to /user_login with HTTP parameters username and password.
You need a similar view for the admin login that does a POST to /admin_login with HTTP parameters username and password.