1. 引言
在这个教程中,我们将学习如何在Spring Security配置中使用两个不同的登录页面,通过配置两个不同的http
元素来实现。
2. 配置2个Http元素
我们可能需要两个登录页面的情况是,当应用程序有一个管理员和一个普通用户的不同登录界面时。我们将配置两个http
元素,它们将通过每个关联的URL模式进行区分:
- **/user***:需要普通用户身份验证才能访问的页面
- **/admin***:由管理员访问的页面
每个http
元素将有不同的登录页面和登录处理URL。
为了配置两个不同的http
元素,我们将创建两个带有@Configuration
注解的静态类。
这两个都将放置在一个常规的@Configuration
类内:
@Configuration
@EnableWebSecurity
public class SecurityConfig {
...
}
现在,让我们定义“ADMIN”用户的ConfigurerAdapter
:
@Configuration
@Order(1)
public static class App1ConfigurationAdapter {
@Bean
public SecurityFilterChain filterChainApp1(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
http.securityMatcher("/admin*")
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry.requestMatchers(mvcMatcherBuilder.pattern("/admin*")).hasRole("ADMIN"))
// log in
.formLogin(httpSecurityFormLoginConfigurer ->
httpSecurityFormLoginConfigurer.loginPage("/loginAdmin")
.loginProcessingUrl("/admin_login")
.failureUrl("/loginAdmin?error=loginError")
.defaultSuccessUrl("/adminPage"))
// logout
.logout(httpSecurityLogoutConfigurer ->
httpSecurityLogoutConfigurer.logoutUrl("/admin_logout")
.logoutSuccessUrl("/protectedLinks")
.deleteCookies("JSESSIONID"))
.exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
httpSecurityExceptionHandlingConfigurer.accessDeniedPage("/403"))
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
接着,定义普通用户的ConfigurerAdapter
:
@Configuration
@Order(2)
public static class App2ConfigurationAdapter {
@Bean
public SecurityFilterChain filterChainApp2(HttpSecurity http, HandlerMappingIntrospector introspector) throws Exception {
MvcRequestMatcher.Builder mvcMatcherBuilder = new MvcRequestMatcher.Builder(introspector);
http.securityMatcher("/user*")
.authorizeHttpRequests(authorizationManagerRequestMatcherRegistry ->
authorizationManagerRequestMatcherRegistry.requestMatchers(mvcMatcherBuilder.pattern("/user*")).hasRole("USER"))
// log in
.formLogin(httpSecurityFormLoginConfigurer ->
httpSecurityFormLoginConfigurer.loginPage("/loginUser")
.loginProcessingUrl("/user_login")
.failureUrl("/loginUser?error=loginError")
.defaultSuccessUrl("/userPage"))
// logout
.logout(httpSecurityLogoutConfigurer ->
httpSecurityLogoutConfigurer.logoutUrl("/user_logout")
.logoutSuccessUrl("/protectedLinks")
.deleteCookies("JSESSIONID"))
.exceptionHandling(httpSecurityExceptionHandlingConfigurer ->
httpSecurityExceptionHandlingConfigurer.accessDeniedPage("/403"))
.csrf(AbstractHttpConfigurer::disable);
return http.build();
}
}
请注意,通过在每个静态类上放置@Order
注解,我们指定了在请求URL匹配时两个类被考虑的顺序。
两个配置类不能有相同的顺序。
3. 定制登录页面
我们将为每种类型的用户创建自定义登录页面。对于管理员用户,登录表单的动作将按照配置设定为"user_login"
:
<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>
管理员的登录页面类似,但表单动作将根据Java配置设定为"admin_login"
。
4. 身份验证配置
现在我们需要为我们的应用程序配置身份验证。让我们看看两种实现方法——一种是使用共同的用户身份验证源,另一种是使用两个独立的身份验证源。
4.1. 使用共同的用户身份验证源
如果两个登录页面共享一个共同的用户身份验证源,你可以创建一个类型为UserDetailsService
的单例bean,它将处理身份验证。
让我们用一个InMemoryUserDetailsManager
示例来演示这种情况,它定义了两个用户——一个具有“USER”角色,另一个具有“ADMIN”角色:
@Bean
public UserDetailsService userDetailsService() throws Exception {
InMemoryUserDetailsManager manager = new InMemoryUserDetailsManager();
manager.createUser(User
.withUsername("user")
.password(encoder().encode("userPass"))
.roles("USER")
.build());
manager.createUser(User
.withUsername("admin")
.password(encoder().encode("adminPass"))
.roles("ADMIN")
.build());
return manager;
}
@Bean
public static PasswordEncoder encoder() {
return new BCryptPasswordEncoder();
}
4.2. 使用两个不同的用户身份验证源
如果你有两个不同的用户身份验证源——一个是管理员,一个是普通用户——你可以在每个静态@Configuration
类中配置一个AuthenticationManagerBuilder
。以下是为“ADMIN”用户配置身份管理器的一个例子:
@Configuration
@Order(1)
public static class App1ConfigurationAdapter {
@Bean
public UserDetailsService userDetailsServiceApp1() {
UserDetails user = User.withUsername("admin")
.password(encoder().encode("admin"))
.roles("ADMIN")
.build();
return new InMemoryUserDetailsManager(user);
}
}
在这种情况下,上一节中的UserDetailsService
bean将不再使用。
6. 总结
在这篇快速教程中,我们展示了如何在同一个Spring Security应用中实现两个不同的登录页面。
本文的完整代码可以在GitHub项目中找到。
运行应用时,你可以在*/protectedLinks* URI上查看上述示例。