SpringSecurity 核心过滤器——SecurityContextPersistenceFilter

简介: SecurityContextHolder,这个是一个非常基础的对象,存储了当前应用的上下文SecurityContext,而在SecurityContext可以获取Authentication对象。也就是当前认证的相关信息会存储在Authentication对象中。

@[TOC]
在这里插入图片描述

前言

SecurityContextHolder,这个是一个非常基础的对象,存储了当前应用的上下文SecurityContext,而在SecurityContext可以获取Authentication对象。也就是当前认证的相关信息会存储在Authentication对象中。

image.png

默认情况下,SecurityContextHolder是通过 ThreadLocal来存储对应的信息的。也就是在一个线程中我们可以通过这种方式来获取当前登录的用户的相关信息。而在SecurityContext中就只提供了对Authentication对象操作的方法。

在项目中可以通过以下方式获取当前登录的用户信息

    public String hello(){
   
   
        Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
        Object principal = authentication.getPrincipal();
        if(principal instanceof UserDetails){
   
   
            UserDetails userDetails = (UserDetails) principal;
            System.out.println(userDetails.getUsername());
            return "当前登录的账号是:" + userDetails.getUsername();
        }
        return "当前登录的账号-->" + principal.toString();
    }

调用 getContext()返回的对象是 SecurityContext接口的一个实例,这个对象就是保存在线程中的。接下来将看到,Spring Security中的认证大都返回一个 UserDetails的实例作为principa。

过滤器介绍

在Session中维护一个用户的安全信息就是这个过滤器处理的。从request中获取session,从Session中取出已认证用户的信息保存在SecurityContext中,提高效率,避免每一次请求都要解析用户认证信息,方便接下来的filter直接获取当前的用户信息。

用户信息的存储

用户信息的存储是通过SecutiryContextRepository接口操作的,定义了对SecurityContext的存储操作,在该接口中定义了如下的几个方法

public interface SecurityContextRepository {

    /**
     * 获取SecurityContext对象
     */
    SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder);

    /**
     * 存储SecurityContext
     */
    void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response);

    /**
     * 判断是否存在SecurityContext对象
     */
    boolean containsContext(HttpServletRequest request);

}

默认的实现是HttpSessionSecurityContextRepository。也就是把SecurityContext存储在了HttpSession中。对应的抽象方法实现如下:

获取用户信息

    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
   
   
        // 获取对有的Request和Response对象
        HttpServletRequest request = requestResponseHolder.getRequest();
        HttpServletResponse response = requestResponseHolder.getResponse();
        // 获取HttpSession对象
        HttpSession httpSession = request.getSession(false);
        // 从HttpSession中获取SecurityContext对象
        SecurityContext context = readSecurityContextFromSession(httpSession);
        if (context == null) {
   
   
            // 如果HttpSession中不存在SecurityContext对象就创建一个
            // SecurityContextHolder.createEmptyContext();
            // 默认是ThreadLocalSecurityContextHolderStrategy存储在本地线程中
            context = generateNewContext();
            if (this.logger.isTraceEnabled()) {
   
   
                this.logger.trace(LogMessage.format("Created %s", context));
            }
        }
        // 包装Request和Response对象
        SaveToSessionResponseWrapper wrappedResponse = new SaveToSessionResponseWrapper(response, request,
                httpSession != null, context);
        requestResponseHolder.setResponse(wrappedResponse);
        requestResponseHolder.setRequest(new SaveToSessionRequestWrapper(request, wrappedResponse));
        return context;
    }

存储用户信息

    @Override
    public void saveContext(SecurityContext context, HttpServletRequest request, HttpServletResponse response) {
   
   
        SaveContextOnUpdateOrErrorResponseWrapper responseWrapper = WebUtils.getNativeResponse(response,
                SaveContextOnUpdateOrErrorResponseWrapper.class);
        Assert.state(responseWrapper != null, () -> "Cannot invoke saveContext on response " + response
                + ". You must use the HttpRequestResponseHolder.response after invoking loadContext");

        responseWrapper.saveContext(context);
    }
@Override
        protected void saveContext(SecurityContext context) {
   
   
            // 获取Authentication对象
            final Authentication authentication = context.getAuthentication();
            // 获取HttpSession对象
            HttpSession httpSession = this.request.getSession(false);
            // 
            String springSecurityContextKey = HttpSessionSecurityContextRepository.this.springSecurityContextKey;
            // See SEC-776
            if (authentication == null
                    || HttpSessionSecurityContextRepository.this.trustResolver.isAnonymous(authentication)) {
   
   
                if (httpSession != null && this.authBeforeExecution != null) {
   
   
                    // SEC-1587 A non-anonymous context may still be in the session
                    // SEC-1735 remove if the contextBeforeExecution was not anonymous
                    httpSession.removeAttribute(springSecurityContextKey);
                    this.isSaveContextInvoked = true;
                }
                if (this.logger.isDebugEnabled()) {
   
   
                    if (authentication == null) {
   
   
                        this.logger.debug("Did not store empty SecurityContext");
                    }
                    else {
   
   
                        this.logger.debug("Did not store anonymous SecurityContext");
                    }
                }
                return;
            }
            httpSession = (httpSession != null) ? httpSession : createNewSessionIfAllowed(context, authentication);
            // If HttpSession exists, store current SecurityContext but only if it has
            // actually changed in this thread (see SEC-37, SEC-1307, SEC-1528)
            if (httpSession != null) {
   
   
                // We may have a new session, so check also whether the context attribute
                // is set SEC-1561
                if (contextChanged(context) || httpSession.getAttribute(springSecurityContextKey) == null) {
   
   
                    // HttpSession 中存储SecurityContext
                    httpSession.setAttribute(springSecurityContextKey, context);
                    this.isSaveContextInvoked = true;
                    if (this.logger.isDebugEnabled()) {
   
   
                        this.logger.debug(LogMessage.format("Stored %s to HttpSession [%s]", context, httpSession));
                    }
                }
            }
        }

获取用户信息

    @Override
    public boolean containsContext(HttpServletRequest request) {
   
   
        // 获取HttpSession
        HttpSession session = request.getSession(false);
        if (session == null) {
   
   
            return false;
        }
        // 从session中能获取就返回true否则false
        return session.getAttribute(this.springSecurityContextKey) != null;
    }

处理逻辑

在请求到达时,SecurityContextPersistenceFilter会从HTTP请求中读取用户凭证,并使用这些信息来恢复用户的安全上下文。在请求完成后,它会在HTTP响应中写入用户凭证,以便在下一次请求时可以恢复用户的安全上下文。具体处理逻辑:

    private void doFilter(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
            throws IOException, ServletException {
   
   
        // 同一个请求之处理一次
        if (request.getAttribute(FILTER_APPLIED) != null) {
   
   
            chain.doFilter(request, response);
            return;
        }
        // 更新状态
        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);
        // 是否提前创建 HttpSession
        if (this.forceEagerSessionCreation) {
   
   
            // 创建HttpSession
            HttpSession session = request.getSession();
            if (this.logger.isDebugEnabled() && session.isNew()) {
   
   
                this.logger.debug(LogMessage.format("Created session %s eagerly", session.getId()));
            }
        }
        // 把Request和Response对象封装为HttpRequestResponseHolder对象
        HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
        // 获取SecurityContext对象
        SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
        try {
   
   
            // SecurityContextHolder绑定SecurityContext对象
            SecurityContextHolder.setContext(contextBeforeChainExecution);
            if (contextBeforeChainExecution.getAuthentication() == null) {
   
   
                logger.debug("Set SecurityContextHolder to empty SecurityContext");
            }
            else {
   
   
                if (this.logger.isDebugEnabled()) {
   
   
                    this.logger
                            .debug(LogMessage.format("Set SecurityContextHolder to %s", contextBeforeChainExecution));
                }
            }// 结束交给下一个过滤器处理
            chain.doFilter(holder.getRequest(), holder.getResponse());
        }
        finally {
   
   
            // 当其他过滤器都处理完成后
            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            // 移除SecurityContextHolder中的Security
            SecurityContextHolder.clearContext();
            // 把
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
            // 存储Context在HttpSession中
            request.removeAttribute(FILTER_APPLIED);
            this.logger.debug("Cleared SecurityContextHolder to complete request");
        }
    }

通过上面的代码逻辑其实就清楚了在SpringSecurity中的认证信息的流转方式了。首先用户的认证状态Authentication是存储在SecurityContext中的,而每个用户的SecurityContext是统一存储在HttpSession中的。一次请求流转中我们需要获取当前的认证信息是通过SecurityContextHolder来获取的,默认是在ThreadLocal中存储的。

总结

SecurityContextPersistenceFilter是Spring Security框架中一个重要的过滤器,用于处理与用户安全上下文相关的操作,通常位于Spring Security过滤器链的最前面,因为需要在其他过滤器执行之前恢复用户的安全上下文。在Spring Security的过滤器链中,SecurityContextPersistenceFilter之后的过滤器可能会改变用户的身份或安全上下文,例如AuthenticationFilter可能会对用户的身份进行认证。

相关文章
|
网络协议 Windows
两步带你解决IDEA 插件下载安装慢、超时、不成功问题
这篇文章提供了解决IDEA插件下载慢或超时问题的方案,通过查找国内插件节点IP地址并修改本地hosts文件来加速下载。
两步带你解决IDEA 插件下载安装慢、超时、不成功问题
|
9月前
|
存储 安全 Java
Spring Security 入门与详解
Spring Security 是 Spring 框架中的核心安全模块,提供认证、授权及防护功能。本文详解其核心概念,包括认证(Authentication)、授权(Authorization)和过滤器链(Security Filter Chain)。同时,通过代码示例介绍基本配置,如 PasswordEncoder、UserDetailsService 和自定义登录页面等。最后总结常见问题与解决方法,助你快速掌握 Spring Security 的使用与优化。
2467 0
|
消息中间件 NoSQL Java
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
Redis Streams在Spring Boot中的应用:构建可靠的消息队列解决方案【redis实战 二】
8742 1
|
6月前
|
机器学习/深度学习 XML Java
【spring boot logback】日志logback格式解析
在 Spring Boot 中,Logback 是默认的日志框架,它支持灵活的日志格式配置。通过配置 logback.xml 文件,可以定义日志的输出格式、日志级别、日志文件路径等。
1223 5
|
安全 前端开发 Java
Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的
上一篇主要介绍了Spring Secuirty中的过滤器链SecurityFilterChain是如何配置的,那么在配置完成之后,SecurityFilterChain是如何在应用程序中调用各个Filter,从而起到安全防护的作用,本文主要围绕SecurityFilterChain的工作原理做详细的介绍。
1511 0
Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的
|
缓存 安全 Java
Spring Boot 3 集成 Spring Security + JWT
本文详细介绍了如何使用Spring Boot 3和Spring Security集成JWT,实现前后端分离的安全认证概述了从入门到引入数据库,再到使用JWT的完整流程。列举了项目中用到的关键依赖,如MyBatis-Plus、Hutool等。简要提及了系统配置表、部门表、字典表等表结构。使用Hutool-jwt工具类进行JWT校验。配置忽略路径、禁用CSRF、添加JWT校验过滤器等。实现登录接口,返回token等信息。
6312 13
Spring Boot 3 集成 Spring Security + JWT
|
前端开发 JavaScript 网络安全
request.getSession().getAttribute 获取不到值,获取到的是null
这篇文章讨论了在使用前后端分离架构时,通过AJAX请求进行会话管理时遇到的跨域问题,导致`request.getSession().getAttribute`获取到的值为null。解决办法是设置`withCredentials=true`以允许跨域请求携带cookie,确保请求凭证得以传递。
|
算法 Java
Java数据结构与算法:最短路径算法
Java数据结构与算法:最短路径算法
|
11月前
|
Cloud Native Java Nacos
springcloud/springboot集成NACOS 做注册和配置中心以及nacos源码分析
通过本文,我们详细介绍了如何在 Spring Cloud 和 Spring Boot 中集成 Nacos 进行服务注册和配置管理,并对 Nacos 的源码进行了初步分析。Nacos 作为一个强大的服务注册和配置管理平台,为微服务架构提供
4518 14
|
存储 Java Spring
深入理解org.springframework.web.context.request.RequestContextHolder
`RequestContextHolder`是Spring提供的一个便捷工具类,用于在非Controller层访问请求上下文信息。通过理解其工作原理和应用场景,可以更好地在Spring应用中管理和使用请求信息,提升代码的可维护性和扩展性。
685 0