Spring Security 6.x 系列(6)—— 显式设置和修改登录态信息

一、前言

此篇是对上篇 Spring Security 6.x 系列(5)------ Servlet 认证体系结构介绍4.9 章节显式调用SecurityContextRepository#saveContext进行详解分析。

二、设置和修改登录态

2.1 登录态存储形式

使用Spring Security框架,认证成功后的用户信息会放在Authentication对象的Principal中,
Authentication对象又会被放入SecurityContext中,而SecurityContext 存在这2个地方:

  • SecurityContextHolderStrategy :线程级别的SecurityContext持有策略。有全局共享、线程继承、线程隔离等几种获取上下文的方式(上文有过介绍)。

  • SecurityContextRepository:持久化SecurityContext ,默认存入HttpServletRequestHttpSession

在代码中,我们要获取用户登录信息,可以通过SecurityContextHolder.getContext().getAuthentication()的方式获取,这种方式是从SecurityContextHolderStrategy获取用户数据。而SecurityContextHolderStrategy初始化数据又是来自SecurityContextRepository,相关逻辑是在SecurityContextHolderFilter类里。

SecurityContextHolderFilter#doFilter源码如下:

查看securityContextRepository如下:

设想一种场景,在用户登录后,编辑了用户信息,这时要同步刷新SecurityContext里的用户信息。我们要如何更新这两个处的用户数据呢?

2.2 更新SecurityContextHolderStrategy中的用户信息

要更新SecurityContextHolderStrategy非常简单,因为它保存在内存里,只要通过SecurityContextHolder.getContext().getAuthentication() 获取认证信息后,直接设置对应的属性,内存中属性值发生变化,后续处理逻辑就能读到最新值。

java 复制代码
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
MyUser myUser = (MyUser) authentication.getPrincipal();
myUser.setNickname("新的昵称");

2.3 更新SecurityContextRepository中的用户信息

2.3.1 了解SecurityContextRepository接口

SecurityContextRepository是一个接口,源码如下:

java 复制代码
public interface SecurityContextRepository {
    /** @deprecated */
    @Deprecated
    SecurityContext 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接口有以下几个实现类:

  • NullSecurityContextRepository:什么都不做,也就是不会存储,每次请求都需要重新认证
  • HttpSessionSecurityContextRepository :将SecurityContext保存在Session中,获取的时候,从Session中查询
  • RequestAttributeSecurityContextRepository :将SecurityContext保存在请求对象HttpServletRequest
  • DelegatingSecurityContextRepository :委派代理存储,内部维护多个SecurityContextRepository,实现同时支持多种方式存储SecurityContext

2.3.2 无法直接获取SecurityContextRepository对象

要知道怎么更新SecurityContextRepository ,我们先看其他地方是怎么调用它。往SecurityContextRepository写数据是在用户认证成功之后,调用AbstractAuthenticationProcessingFilter#successfulAuthentication() 方法,执行认证成功的后续逻辑时。


这里的this.securityContextRepository是通过setter方法传进来的,并且发现Spring Security没有把SecurityContextRepository注册到Spring容器,而且Spring Security其他持有SecurityContextRepository对象的类都没有暴露SecurityContextRepository的获取方法。也就是说,我们无法从Spring Security拿到默认的SecurityContextRepository对象。

debug发现this.securityContextRepository属性指向了 DelegatingSecurityContextRepository,这是委派代理存储,代理的对象是 RequestAttributeSecurityContextRepositoryHttpSessionSecurityContextRepository,所以在默认的情况下,用户登录成功之后,在这里就把登录用户数据分别存入到HttpSessionSecurityContextRepositoryRequestAttributeSecurityContextRepository中。

部分DelegatingSecurityContextRepository源码如下:


2.3.3 手动设置SecurityContextRepository对象

为了顺利拿到SecurityContextRepository对象,我们可以手动往Spring容器注册一个SecurityContextRepository对象,然后把它塞到Spring Security里。通过这种方式,我们能从Spring容器拿到SecurityContextRepository 对象,然后随时刷新SecurityContext

具体实现代码如下:

java 复制代码
@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())
        );
}

这里DelegatingSecurityContextRepositorySpring Security中的SecurityContextRepository默认实现,我们原封不动保留下来。

2.3.4 更新登录态

在更新完SecurityContextHolderStrategy 对象之后,我们显式把SecurityContext重新保存到SecurityContextRepository

java 复制代码
// 更新SecurityContextHolderStrategy
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
MyUser myUser = (MyUser) authentication.getPrincipal();
myUser.setNickname("新的昵称");

// 更新SecurityContextRepository
securityContextRepository.saveContext(SecurityContextHolder.getContext(), request, response);

三、总结

通过显式更新SecurityContextHolderStrategySecurityContextRepository ,我们就能完整更新SecurityContext 中的用户信息。如果项目中引入了Spring SessionSpring Session维护的登录态也会同步更新。

相关推荐
那你为何对我三笑留情10 小时前
六、Spring Boot集成Spring Security之前后分离项目认证流程最佳方案
java·spring boot·分布式·后端·spring·spring security
ccmjga9 天前
建造者设计模式
java·spring boot·设计模式·gradle·spring security·1024程序员节
ccmjga11 天前
为什么选择 Spring data hadoop
java·spring boot·docker·设计模式·gradle·spring security
ccmjga14 天前
适配器设计模式
java·spring boot·后端·设计模式·gradle·spring security·1024程序员节
那你为何对我三笑留情24 天前
五、Spring Boot集成Spring Security之认证流程2
spring boot·spring security
那你为何对我三笑留情1 个月前
三、Spring Boot集成Spring Security之securityFilterChain过滤器链详解
java·spring boot·spring·spring security·过滤器链
那你为何对我三笑留情1 个月前
二、Spring Boot集成Spring Security之实现原理
java·spring boot·spring·spring security
那你为何对我三笑留情1 个月前
一、Spring Boot集成Spring Security之自动装配
java·spring boot·spring·spring security