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可能会对用户的身份进行认证。

相关文章
|
6月前
|
Java 开发者 Spring
Spring项目中Ordered接口的应用:全局过滤器(GlobalFilter)的顺序控制
Spring项目中Ordered接口的应用:全局过滤器(GlobalFilter)的顺序控制
262 2
|
6月前
|
Java API 数据安全/隐私保护
在Spring Boot中,过滤器(Filter)是一种非常有用的组件
在Spring Boot中,过滤器(Filter)是一种非常有用的组件
91 6
|
6月前
|
安全 前端开发 Java
Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的
上一篇主要介绍了Spring Secuirty中的过滤器链SecurityFilterChain是如何配置的,那么在配置完成之后,SecurityFilterChain是如何在应用程序中调用各个Filter,从而起到安全防护的作用,本文主要围绕SecurityFilterChain的工作原理做详细的介绍。
387 0
Spring Security 6.x 过滤器链SecurityFilterChain是如何工作的
|
6月前
|
安全 Java Spring
SpringSecurity6从入门到实战之Filter过滤器回顾
该文主要介绍了SpringSecurity框架中的过滤器Filter,探讨了在没有SpringSecurity时如何检查用户登录状态以保护资源。文中通过流程图展示了过滤器在HTTP请求处理中的位置,并提供了官方和中文文档链接。过滤器需实现Filter接口,用于拦截请求并进行预处理和后处理,例如强制登录检查。过滤器链FilterChain则是一系列Filter和资源的组合,通过doFilter方法逐个调用下一个过滤器或传递到目标资源。
|
存储 安全 NoSQL
SpringSecurity 核心过滤器——CsrfFilter
Spring Security除了认证授权外功能外,还提供了安全防护功能。本文我们来介绍下SpringSecurity中是如何阻止CSRF攻击的。
289 0
|
SQL 监控 前端开发
Springboot过滤器和拦截器详解及使用场景
过滤器和拦截器触发时机不一样,过滤器是在请求进入容器后,但请求进入servlet之前进行预处理的。请求结束返回也是,是在servlet处理完后,返回给前端之前。
|
Java Spring
Spring Boot 监听器、拦截器以及过滤器的作用、差异?
Spring Boot 监听器、拦截器以及过滤器的作用、差异?
231 0
boot 中自定义shiro过滤器的拦截顺序
@boot 中filter SecurityUtils.getSubject()No SecurityManager accessible
363 0
boot 中自定义shiro过滤器的拦截顺序
|
安全 Java 开发者
Spring Security过滤器链加载原理|学习笔记
快速学习 Spring Security 过滤器链加载原理
Spring Security过滤器链加载原理|学习笔记
|
监控 前端开发 JavaScript
Spring模块 | 拦截器如何实现
Spring根据官方文档实现拦截器
118 0