1. 概述
一個常見的要求是,對於Web應用程序來説,在用户登錄後將不同類型的用户重定向到不同的頁面。例如,將標準用户重定向到 /homepage.html 頁面,而管理員用户則重定向到 /console.html 頁面。
本文將展示如何使用 Spring Security 快速且安全地實現此機制。本文還建立在 Spring MVC 教程的基礎上,該教程處理了項目所需的核心 MVC 設置。
2. The Spring Security Configuration
Spring Security 提供一個組件,負責在成功認證後執行的操作——AuthenticationSuccessHandler
2.1. Basic Configuration
首先配置一個基本的 @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 – Roles and Privileges。
@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. Adding the Custom Success Handler
我們現在有兩個用户,擁有兩個不同的角色: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 Configuration
在查看我們自定義成功處理程序的實現之前,讓我們也看一下 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 將是第一個角色名稱匹配的映射 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 確定——將重定向到主頁。