登錄態存儲形式
使用Spring Security框架,用戶認證成功后的用戶信息會放在Authentication
對象的Principal中。Authentication
對象又會放入SecurityContext
,而SecurityContext
存在這2個地方:
-
SecurityContextHolderStrategy
:線程級別的SecurityContext
持有策略。有全局共享、線程繼承、線程隔離等幾種獲取上下文的方式。 -
SecurityContextRepository
:持久化SecurityContext
,默認存入HttpServletRequest
和HttpSession
。
在代碼中,我們要獲取用戶登錄信息,可以通過SecurityContextHolder.*getContext*().getAuthentication()
的方式獲取,這種方式是從SecurityContextHolderStrategy
獲取用戶數據。而SecurityContextHolderStrategy
初始化數據又是來自SecurityContextRepository
,相關邏輯是在SecurityContextHolderFilter
類里。
設想一種場景,在用戶登錄后,編輯了用戶信息,這時要同步刷新SecurityContext
里的用戶信息。我們要如何更新這兩個處的用戶數據呢?
更新SecurityContextHolderStrategy
中的用戶信息
要更新SecurityContextHolderStrategy
非常簡單,因為它保存在內存里,只要通過SecurityContextHolder.*getContext*().getAuthentication()
獲取認證信息后,直接設置對應的屬性,內存中屬性值發生變化,后續處理邏輯就能讀到最新值。
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
MyUser myUser = (MyUser) authentication.getPrincipal();
myUser.setNickname("新的昵稱");
更新SecurityContextRepository
中的用戶信息
無法直接獲取SecurityContextRepository
對象
要知道怎么更新SecurityContextRepository
,我們先看其他地方是怎么調用它。往SecurityContextRepository
寫數據是在用戶認證成功之后,調用AbstractAuthenticationProcessingFilter#successfulAuthentication()
方式執行認證成功的后續邏輯時。
protected void successfulAuthentication(HttpServletRequest request, HttpServletResponse response, FilterChain chain,
Authentication authResult) throws IOException, ServletException {
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authResult);
this.securityContextHolderStrategy.setContext(context);
// 這里寫入securityContextRepository
this.securityContextRepository.saveContext(context, request, response);
...
}
這里的this.securityContextRepository是通過setter方法傳進來的,并且發現Spring Security沒有把SecurityContextRepository
注冊到Spring容器,而且Spring Security其他持有SecurityContextRepository
對象的類都沒有暴露SecurityContextRepository
的獲取方法。也就是說,我們無法從Spring Security拿到默認的SecurityContextRepository
對象。
手動設置SecurityContextRepository
對象
為了順利拿到SecurityContextRepository
對象,我們可以手動往Spring容器注冊一個SecurityContextRepository
對象,然后把它塞到Spring Security里。通過這種方式,我們能從Spring容器拿到SecurityContextRepository
對象,然后隨時刷新SecurityContext
。
Spring Security設置SecurityContextRepository
的方式是:
@Bean
public SecurityContextRepository securityContextRepository() {
return new DelegatingSecurityContextRepository(
new RequestAttributeSecurityContextRepository(), new HttpSessionSecurityContextRepository());
}
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, SecurityContextRepository securityContextRepository) throws Exception {
httpSecurity
.securityContext(it -> it.securityContextRepository(securityContextRepository))
return httpSecurity.build();
}
這里DelegatingSecurityContextRepository是Spring Security的默認值,我們原封不動保留下來。
更新登錄態
在更新完SecurityContextHolderStrategy
對象之后,我們把SecurityContext
重新保存到SecurityContextRepository
。
// 更新SecurityContextHolderStrategy
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
MyUser myUser = (MyUser) authentication.getPrincipal();
myUser.setNickname("新的昵稱");
// 更新SecurityContextRepository
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
總結
通過更新SecurityContextHolderStrategy
和 SecurityContextRepository
,我們就能完整更新SecurityContext
中的用戶信息。如果項目中引入了Spring Session,Spring Session維護的登錄態也會同步更新。