1. 概述
Web應用程序的常見需求是在登錄後將不同類型的用户重定向到不同的頁面。例如,將標準用户重定向到/homepage.html頁面,而管理用户則重定向到/console.html頁面。
本文將演示如何使用Spring Security快速且安全地實現此機制。本文還將基於Spring MVC教程,該教程處理了項目所需的核心MVC設置。
2. Spring Security 配置
Spring Security 提供了一個組件,其直接職責是決定在成功認證後執行的操作——AuthenticationSuccessHandler。
2.1. 基本配置
讓我們首先配置一個基本的 @Configuration 和 @Service 類:
@Configuration
@EnableWebSecurity
public class SecSecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// ... endpoints
.formLogin(formLogin -> 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));
}
}
請注意,在這個簡單的示例中,我們不會使用密碼編碼器,因此密碼前綴為 {noop}。
2.2. 添加自定義成功處理程序
我們現在有兩個用户,分別擁有 user 和 admin 兩種不同的角色。 在成功登錄後,他們都會被重定向到 hompeage.html。 讓我們看看如何根據用户的角色來設置不同的重定向。
首先,我們需要將自定義的成功處理程序定義為一個 Bean:
@Bean
public AuthenticationSuccessHandler myAuthenticationSuccessHandler(){
return new MySimpleUrlAuthenticationSuccessHandler();
}
然後,將 defaultSuccessUrl 調用替換為 successHandler 方法,該方法接受我們自定義的成功處理器作為參數:
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeRequests()
// endpoints
.formLogin(formLogin -> 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 還提供了一種合理的默認實現——AbstractAuthenticationTargetUrlRequestHandler,以及一個簡單的實現——SimpleUrlAuthenticationSuccessHandler。 通常,這些實現將確定登錄後的 URL,並執行重定向到該 URL。
雖然該機制具有一定的靈活性,但它不允許通過編程方式確定目標 URL,因此我們將實現該接口並提供自定義的成功處理器的實現。 此實現將根據用户的角色確定重定向用户到 URL。
首先,我們需要覆蓋 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);
}
我們的自定義方法調用了兩個輔助方法:
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);
}
以下方法執行實際工作,並將用户映射到目標 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();
}
請注意,此方法將返回用户擁有的第一個角色的映射 URL。如果用户擁有多個角色,映射 URL 將是 authorities 集合中提供的第一個角色所匹配的 URL。
protected void clearAuthenticationAttributes(HttpServletRequest request) {
HttpSession session = request.getSession(false);
if (session == null) {
return;
}
session.removeAttribute(WebAttributes.AUTHENTICATION_EXCEPTION);
}determineTargetUrl – 它是策略的核心 – 僅根據用户類型(由權威確定)和根據此角色選擇目標 URL。
因此,ROLE_ADMIN 權威確定的管理員用户 將在登錄後重定向到控制枱頁面,而ROLE_USER 權威確定的普通用户 將重定向到主頁。