使用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维护的登录态也会同步更新。