建立健全制度如何推廣seo
一、前言
此篇是對(duì)上篇 Spring Security 6.x 系列(5)—— Servlet 認(rèn)證體系結(jié)構(gòu)介紹 中4.9章節(jié)顯式調(diào)用SecurityContextRepository#saveContext
進(jìn)行詳解分析。
二、設(shè)置和修改登錄態(tài)
2.1 登錄態(tài)存儲(chǔ)形式
使用Spring Security
框架,認(rèn)證成功后的用戶信息會(huì)放在Authentication
對(duì)象的Principal
中,
Authentication
對(duì)象又會(huì)被放入SecurityContext
中,而SecurityContext
存在這2個(gè)地方:
-
SecurityContextHolderStrategy
:線程級(jí)別的SecurityContext
持有策略。有全局共享、線程繼承、線程隔離等幾種獲取上下文的方式(上文有過(guò)介紹)。 -
SecurityContextRepository
:持久化SecurityContext
,默認(rèn)存入HttpServletRequest
和HttpSession
。
在代碼中,我們要獲取用戶登錄信息,可以通過(guò)SecurityContextHolder.getContext().getAuthentication()
的方式獲取,這種方式是從SecurityContextHolderStrategy
獲取用戶數(shù)據(jù)。而SecurityContextHolderStrategy
初始化數(shù)據(jù)又是來(lái)自SecurityContextRepository
,相關(guān)邏輯是在SecurityContextHolderFilter
類(lèi)里。
SecurityContextHolderFilter#doFilter
源碼如下:
查看securityContextRepository
如下:
設(shè)想一種場(chǎng)景,在用戶登錄后,編輯了用戶信息,這時(shí)要同步刷新SecurityContext
里的用戶信息。我們要如何更新這兩個(gè)處的用戶數(shù)據(jù)呢?
2.2 更新SecurityContextHolderStrategy中的用戶信息
要更新SecurityContextHolderStrategy
非常簡(jiǎn)單,因?yàn)樗4嬖趦?nèi)存里,只要通過(guò)SecurityContextHolder.getContext().getAuthentication()
獲取認(rèn)證信息后,直接設(shè)置對(duì)應(yīng)的屬性,內(nèi)存中屬性值發(fā)生變化,后續(xù)處理邏輯就能讀到最新值。
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
MyUser myUser = (MyUser) authentication.getPrincipal();
myUser.setNickname("新的昵稱");
2.3 更新SecurityContextRepository中的用戶信息
2.3.1 了解SecurityContextRepository接口
SecurityContextRepository
是一個(gè)接口,源碼如下:
public interface SecurityContextRepository {/** @deprecated */@DeprecatedSecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);default DeferredSecurityContext loadDeferredContext(HttpServletRequest request) {Supplier<SecurityContext> supplier = () -> {return this.loadContext(new HttpRequestResponseHolder(request, (HttpServletResponse)null));};return new SupplierDeferredSecurityContext(SingletonSupplier.of(supplier), SecurityContextHolder.getContextHolderStrategy());}void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);boolean containsContext(HttpServletRequest request);
}
SecurityContextRepository
接口有以下幾個(gè)實(shí)現(xiàn)類(lèi):
- NullSecurityContextRepository:什么都不做,也就是不會(huì)存儲(chǔ),每次請(qǐng)求都需要重新認(rèn)證
- HttpSessionSecurityContextRepository:將
SecurityContext
保存在Session
中,獲取的時(shí)候,從Session
中查詢 - RequestAttributeSecurityContextRepository:將
SecurityContext
保存在請(qǐng)求對(duì)象HttpServletRequest
中 - DelegatingSecurityContextRepository:委派代理存儲(chǔ),內(nèi)部維護(hù)多個(gè)
SecurityContextRepository
,實(shí)現(xiàn)同時(shí)支持多種方式存儲(chǔ)SecurityContext
。
2.3.2 無(wú)法直接獲取SecurityContextRepository對(duì)象
要知道怎么更新SecurityContextRepository
,我們先看其他地方是怎么調(diào)用它。往SecurityContextRepository
寫(xiě)數(shù)據(jù)是在用戶認(rèn)證成功之后,調(diào)用AbstractAuthenticationProcessingFilter#successfulAuthentication()
方法,執(zhí)行認(rèn)證成功的后續(xù)邏輯時(shí)。
這里的this.securityContextRepository
是通過(guò)setter
方法傳進(jìn)來(lái)的,并且發(fā)現(xiàn)Spring Security
沒(méi)有把SecurityContextRepository
注冊(cè)到Spring
容器,而且Spring Security
其他持有SecurityContextRepository
對(duì)象的類(lèi)都沒(méi)有暴露SecurityContextRepository
的獲取方法。也就是說(shuō),我們無(wú)法從Spring Security
拿到默認(rèn)的SecurityContextRepository
對(duì)象。
debug
發(fā)現(xiàn)this.securityContextRepository
屬性指向了 DelegatingSecurityContextRepository
,這是委派代理存儲(chǔ),代理的對(duì)象是 RequestAttributeSecurityContextRepository
和 HttpSessionSecurityContextRepository
,所以在默認(rèn)的情況下,用戶登錄成功之后,在這里就把登錄用戶數(shù)據(jù)分別存入到HttpSessionSecurityContextRepository
和RequestAttributeSecurityContextRepository
中。
部分DelegatingSecurityContextRepository
源碼如下:
2.3.3 手動(dòng)設(shè)置SecurityContextRepository對(duì)象
為了順利拿到SecurityContextRepository
對(duì)象,我們可以手動(dòng)往Spring
容器注冊(cè)一個(gè)SecurityContextRepository
對(duì)象,然后把它塞到Spring Security
里。通過(guò)這種方式,我們能從Spring
容器拿到SecurityContextRepository
對(duì)象,然后隨時(shí)刷新SecurityContext
。
具體實(shí)現(xiàn)代碼如下:
@Bean
public SecurityContextRepository securityContextRepository() {return new DelegatingSecurityContextRepository(new RequestAttributeSecurityContextRepository(), new HttpSessionSecurityContextRepository());
}@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity httpSecurity, SecurityContextRepository securityContextRepository) throws Exception {httpSecurity.securityContext((context) -> context.securityContextRepository(securityContextRepository()));
}
這里DelegatingSecurityContextRepository
是Spring Security
中的SecurityContextRepository
默認(rèn)實(shí)現(xiàn),我們?cè)獠粍?dòng)保留下來(lái)。
2.3.4 更新登錄態(tài)
在更新完SecurityContextHolderStrategy
對(duì)象之后,我們顯式把SecurityContext
重新保存到SecurityContextRepository
。
// 更新SecurityContextHolderStrategy
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
MyUser myUser = (MyUser) authentication.getPrincipal();
myUser.setNickname("新的昵稱");// 更新SecurityContextRepository
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);
三、總結(jié)
通過(guò)顯式更新SecurityContextHolderStrategy
、SecurityContextRepository
,我們就能完整更新SecurityContext
中的用戶信息。如果項(xiàng)目中引入了Spring Session
,Spring Session
維護(hù)的登錄態(tài)也會(huì)同步更新。