1. 概述
Web应用中一个常见的需求是,用户登录后根据角色重定向到不同的页面。例如,普通用户跳转到/homepage.html
,管理员跳转到/console.html
。
下面将演示如何使用Spring Security实现此功能。 本文涉及MVC相关知识,如果不熟悉可参考我们之前的Spring MVC教程。
2. Spring Security 配置
Spring Security 提供了一个组件 —— AuthenticationSuccessHandler
,从名字上我们也能看出,它负责处理登录成功后的逻辑操作。
2.1. 基本配置
首先定义我们的 Security 配置类
@Configuration
@EnableWebSecurity
public class SecSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// ... endpoints
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.defaultSuccessUrl("/homepage.html", true)
// ... other configuration
return http.build();
}
}
上面配置中我们需要重点关注defaultSuccessUrl()
这项配置。它表示用户登录成功后,将重定向到/homepage.html
。
此外,我们还需要配置用户和角色信息。我们将实现一个简单的UserDetailService
,其中定义两个用户,每个用户对应一个角色。想了解更多关于此话题信息,请阅读Spring Security – 角色与权限。
@Service
public class MyUserDetailsService implements UserDetailsService {
private Map<String, User> roles = new HashMap<>();
@PostConstruct
public void init() {
roles.put("admin2", new User("admin", "{noop}admin1", getAuthority("ROLE_ADMIN")));
roles.put("user2", new User("user", "{noop}user1", getAuthority("ROLE_USER")));
}
@Override
public UserDetails loadUserByUsername(String username) {
return roles.get(username);
}
private List<GrantedAuthority> getAuthority(String role) {
return Collections.singletonList(new SimpleGrantedAuthority(role));
}
}
注意,本示例中,我们没有使用PasswordEncoder
密码编码器,所以需要在密码中添加{noop}
前缀标识。
2.2. 添加自定义AuthenticationSuccessHandler
现在我们定义了2个不同的用户,user
普通用户 和admin
管理员。但目前登录成功后,都会跳到homepage.html
。下面教你如何实现基于用户角色跳转到不同页面。
首先,创建AuthenticationSuccessHandler
bean(它的定义见下文):
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new MySimpleUrlAuthenticationSuccessHandler();
}
然后,把defaultSuccessUrl
替换为successHandler
方法,并将自定义的AuthenticationSuccessHandler
实例作为参数传进去:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// endpoints
.formLogin()
.loginPage("/login.html")
.loginProcessingUrl("/login")
.successHandler(myAuthenticationSuccessHandler())
// other configuration
return http.build();
}
2.3. XML 配置
等效的XML配置如下:
<http use-expressions="true" >
<!-- other configuration -->
<form-login login-page='/login.html'
authentication-failure-url="/login.html?error=true"
authentication-success-handler-ref="myAuthenticationSuccessHandler"/>
<logout/>
</http>
<beans:bean id="myAuthenticationSuccessHandler"
class="com.baeldung.security.MySimpleUrlAuthenticationSuccessHandler" />
<authentication-manager>
<authentication-provider>
<user-service>
<user name="user1" password="{noop}user1Pass" authorities="ROLE_USER" />
<user name="admin1" password="{noop}admin1Pass" authorities="ROLE_ADMIN" />
</user-service>
</authentication-provider>
</authentication-manager>
3. 自定义AuthenticationSuccessHandler的实现
Spring 内置了一个简单的跳转策略 - SimpleUrlAuthenticationSuccessHandler
,它通过绑定URL、根据请求参数,或根据Referer
进行跳转。
虽然比较灵活,但不能满足我们的需要。所以我们需要实现AuthenticationSuccessHandler
接口,自定义登录成功处理器。
首先我们实现 onAuthenticationSuccess
方法:
public class MySimpleUrlAuthenticationSuccessHandler
implements AuthenticationSuccessHandler {
protected Log logger = LogFactory.getLog(this.getClass());
private RedirectStrategy redirectStrategy = new DefaultRedirectStrategy();
@Override
public void onAuthenticationSuccess(HttpServletRequest request,
HttpServletResponse response, Authentication authentication)
throws IOException {
handle(request, response, authentication);
clearAuthenticationAttributes(request);
}
里面调用了2个helper方法:
protected void handle(
HttpServletRequest request,
HttpServletResponse response,
Authentication authentication
) throws IOException {
String targetUrl = determineTargetUrl(authentication);
if (response.isCommitted()) {
logger.debug(
"Response has already been committed. Unable to redirect to "
+ targetUrl);
return;
}
redirectStrategy.sendRedirect(request, response, targetUrl);
}
determineTargetUrl
方法里面才是我们真正的实现逻辑,根据用户角色决定要重定向的URL:
protected String determineTargetUrl(final Authentication authentication) {
Map<String, String> roleTargetUrlMap = new HashMap<>();
roleTargetUrlMap.put("ROLE_USER", "/homepage.html");
roleTargetUrlMap.put("ROLE_ADMIN", "/console.html");
final Collection<? extends GrantedAuthority> authorities = authentication.getAuthorities();
for (final GrantedAuthority grantedAuthority : authorities) {
String authorityName = grantedAuthority.getAuthority();
if(roleTargetUrlMap.containsKey(authorityName)) {
return roleTargetUrlMap.get(authorityName);
}
}
throw new IllegalStateException();
}
注意 getAuthorities()
返回的是一个集合,所以如果用户有多个角色,那么只会返回第一个角色匹配到的URL。
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}
4. 总结
惯例,本文完整源代码可从GitHub上获取。这是一个基于Maven的项目,所以应该很容易导入和运行。