您现在的位置是:首页 > 正文

security的一个过滤器——SecurityContextPersistenceFilter

2024-01-30 21:03:07阅读 0

security的一个过滤器——SecurityContextPersistenceFilter

1、关于security的用户信息获取

a、SecurityContextHolder.getContext().getAuthentication()
b、在 Controller 的方法中,加入 Authentication 参数,调用getPrincipal()方法

问题异常情况上述两种方式获取用户都为null,即系统获取不到用户信息,报错状态码401,这又是怎么回事呢?
要弄明白这个问题,我们就得明白 Spring Security 中的用户信息到底是在哪里存的?
通过第一种方式security的上下文SecurityContextHolder 中的用户数据,本质上是保存在 ThreadLocal 中,ThreadLocal 的特点是存在它里边的数据,哪个线程存的,哪个线程才能访问到。

这样就带来一个问题,当不同的请求进入到服务端之后,由不同的 thread 去处理,按理说后面的请求就可能无法获取到登录请求的线程存入的数据,例如登录请求在线程 A 中将登录用户信息存入 ThreadLocal,后面的请求来了,在线程 B 中处理,那此时就无法获取到用户的登录信息。

但实际上,正常情况下,我们每次都能够获取到登录用户信息,这又是怎么回事呢?

这我们就要引入 Spring Security 中的 SecurityContextPersistenceFilter 了。security的一系列过滤器链在经过AuthenticationFilter 之前都会先经过 SecurityContextPersistenceFilter这个过滤器。我们来看下源码:

public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
            throws IOException, ServletException {
        HttpServletRequest request = (HttpServletRequest) req;
        HttpServletResponse response = (HttpServletResponse) res;

        if (request.getAttribute(FILTER_APPLIED) != null) 
            chain.doFilter(request, response);
            return;
        }

        final boolean debug = logger.isDebugEnabled();

        request.setAttribute(FILTER_APPLIED, Boolean.TRUE);

        if (forceEagerSessionCreation) {
            HttpSession session = request.getSession();

            if (debug && session.isNew()) {
                logger.debug("Eagerly created session: " + session.getId());
            }
        }

        HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,
                response);
        //利用HttpSecurityContextRepository从HttpSesion中获取SecurityContext对象
        //如果没有HttpSession,即浏览器第一次访问服务器,还没有产生会话。
        //它会创建一个空的SecurityContext对象
        SecurityContext contextBeforeChainExecution = repo.loadContext(holder);

        try {
            //把SecurityContext放入到SecurityContextHolder中
            SecurityContextHolder.setContext(contextBeforeChainExecution);
            //执行拦截链,这个链会逐层向下执行
            chain.doFilter(holder.getRequest(), holder.getResponse());

        }
        finally { 
            //当拦截器都执行完的时候把当前线程对应的SecurityContext从SecurityContextHolder中取出来
            SecurityContext contextAfterChainExecution = SecurityContextHolder
                    .getContext();
            SecurityContextHolder.clearContext();
            //利用HttpSecurityContextRepository把SecurityContext写入HttpSession
            repo.saveContext(contextAfterChainExecution, holder.getRequest(),
                    holder.getResponse());
            request.removeAttribute(FILTER_APPLIED);

            if (debug) {
                logger.debug("SecurityContextHolder now cleared, as request processing completed");
            }
        }
    }
  • doFilter 方法中,它首先会从 repo 中读取一个 SecurityContext 出来,这里的 repo 实际上就是SecurityContextRepository的实现类HttpSessionSecurityContextRepository,读取 SecurityContext 的操作会进入到readSecurityContextFromSession 方法中,
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) {
        boolean debug = this.logger.isDebugEnabled();
        if (httpSession == null) {
            if (debug) {
                this.logger.debug("No HttpSession currently exists");
            }

            return null;
        } else {
        	//此处 this.springSecurityContextKey的值就是SPRING_SECURITY_CONTEXT
            Object contextFromSession = httpSession.getAttribute(this.springSecurityContextKey);
            if (contextFromSession == null) {
                if (debug) {
                    this.logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT");
                }

                return null;
            } else if (!(contextFromSession instanceof SecurityContext)) {
                if (this.logger.isWarnEnabled()) {
                    this.logger.warn(this.springSecurityContextKey + " did not contain a SecurityContext but contained: '" + contextFromSession + "'; are you improperly modifying the HttpSession directly (you should always use SecurityContextHolder) or using the HttpSession attribute reserved for this class?");
                }

                return null;
            } else {
                if (debug) {
                    this.logger.debug("Obtained a valid SecurityContext from " + this.springSecurityContextKey + ": '" + contextFromSession + "'");
                }

                return (SecurityContext)contextFromSession;
            }
        }
    }

在这里我们看到了读取的核心方法 Object
contextFromSession =httpSession.getAttribute(springSecurityContextKey);,这里的
springSecurityContext对象的Key值就是SPRING_SECURITY_CONTEXT,读取出来的对象最终会被转为一个 SecurityContext 对象。

  • SecurityContext 是一个接口,它有一个唯一的实现类SecurityContextImpl,这个实现类其实就是用户信息在 session 中保存的 value。

  • 在拿到SecurityContext 之后,通过 SecurityContextHolder.setContext 方法将这个
    SecurityContext 设置到 ThreadLocal 中去,这样,在当前请求中,Spring Security
    的后续操作,我们都可以直接从 SecurityContextHolder 中获取到用户信息了。

  • 接下来,通过 chain.doFilter 让请求继续向下走(这个时候就会进入到 UsernamePasswordAuthenticationFilter 过滤器中了)。

  • 在过滤器链走完之后,数据响应给前端之后,finally 中还有一步收尾操作,这一步很关键。这里从
    SecurityContextHolder 中获取到 SecurityContext,获取到之后,会把
    SecurityContextHolder 清空,然后调用 repo.saveContext 方法将获取到的
    SecurityContext 存入 session 中。

每一个请求到达服务端的时候,首先从 session 中找出来 SecurityContext ,然后设置到 SecurityContextHolder 中去,方便后续使用,当这个请求离开的时候,SecurityContextHolder 会被清空(此处操作很重要,也是security过滤器链控制安全的一种处理技巧),SecurityContext 会被放回 session 中,方便下一个请求来的时候获取。

那么回到最初的问题,什么时候有获取不到当前用户信息:

1、不经过security过滤器,一个新的线程中去执行SecurityContextHolder.getContext().getAuthentication()

2、SecurityContextPersistenceFilter中的session没有用户信息,如果排除第一种方式,按照该过滤器流程,就是上一个请求结束后,没有将上下文放回到session即finally里面的saveContext方法未执行

那么什么时候会出现第二种情况,就要说到security的放行url机制了,一般在WebSecurityConfigurerAdapter配置类里的两个方法:

1public void configure(WebSecurity web) throws Exception {
		web.ignoring().antMatchers("/xxx","/js/xxx.html");
    }

2protected void configure(HttpSecurity http) throws Exception {
        http.authorizeRequests()
        .antMatchers("/xxx").permitAll()
        .anyRequest().authenticated()
    }

这两个放行的策略区别在于,第一种方式直接没有走security的过滤器,而第二种是经过security过滤器,在过滤器中判断放行

那么有以下结论:

  1. 一般web项目的登陆url放行肯定要走第二种,不经过security过滤器用户信息不会存储到session,后面的请求也无法通过上下文对象获取当前用户信息
  2. 前端静态资源的放行不涉及到用户信息,没必要走security,建议走第一种方式,此为认证,资源无权限特殊情况除外
  3. 额外放行不需要认证的的url,如果采取了第一种方式,那么在当前请求中是无法获取用户信息的,原因就是没有走我们的主角过滤器:SecurityContextPersistenceFilter,要是项目特殊,需要在放行的url里面也获取到用户信息的话,那么可以走第二种配置,有些旧的xml配置(security在mvc项目中未火的主要原因就是xml配置太过繁琐,直至springboot的javaconfig以及自动装配才大放异彩)不支持或是不方便修改,可以手动加一个过滤器模仿SecurityContextPersistenceFilter,将是放行的url提前从session中通过上下文对象SecurityContext的key值SPRING_SECURITY_CONTEXT重新获取下,手动放置SecurityContextHolder中

网站文章

  • 聊聊ctrl+c和ctrl+z的区别

    一句话总结:ctrl+c是强制中断程序,ctrl+c是暂停程序。 Ctrl+C Ctrl+Z Ctrl+D bg fg jobs 强制中断程序,进程终止 暂停程序,挂起 退出shell 将一个在后台暂停的命令,变成继续执行 将后台中的命令调至前台继续运行 查看

    2024-01-30 21:02:36
  • 【蓝桥杯】100个数相乘末尾有几个零

    【蓝桥杯】100个数相乘末尾有几个零

    蓝桥杯-简单计算与模拟部分

    2024-01-30 21:02:23
  • 云时代架构读后感(十二)

    途牛订单的服务化演进 原文地址: https://mp.weixin.qq.com/s?__biz=MzI3MzEzMDI1OQ==&mid=2651814702&idx=1&sn=cafc4aa95db9cfdbd0373d00c633a8fb&scene=21#wechat_redirect 一个系统无论视同开发还是运行时的资源,都无法满足业务的需求...

    2024-01-30 21:02:16
  • 数字图像处理_傅里叶变换_输出矩阵的物理含义分析总结

    考虑二维傅里叶变换。傅里叶变换实现了将图像从空间域到频率域(也叫变换域)的转换,这种转换让我们得到了一个关于原图像灰度信息的频谱图,这个频谱图可以看做是图像梯度的分布图(图像梯度是两个点像素灰度的差值...

    2024-01-30 21:01:46
  • BZOJ2127Happiness

    题目描述高一一班的座位表是个n*m的矩阵,经过一个学期的相处,每个同学和前后左右相邻的同学互相成为了好朋友。这学期要分文理科了,每个同学对于选择文科与理科有着自己的喜悦值,而一对好朋友如果能同时选文科或者理科,那么他们又将收获一些喜悦值。作为计算机竞赛教练的scp大老板,想知道如何分配可以使得全班的喜悦值总和最大。题解这道题相当于给了我们一堆二元关系。容易想到用二元关系最小割来解决。我们设...

    2024-01-30 21:01:39
  • appsettings.json reloadOnChange事件处理

    appsettings.json reloadOnChange事件处理 写作一直是我比较头疼的事,从事10多年软件开发工作,基本上没写过什么文章,.net core的出现让我终于看到了.net平台的曙光,很希望广大.net开发者共同努力让.net core尽...

    2024-01-30 21:01:31
  • 【CCNP】第六章 流量控制 最新发布

    【CCNP】第六章 流量控制 最新发布

    R4(config)# access-list 1 permit 192.168.1.0 0.0.0.255 # 此处的permit不搭配接口下策略使用,不再是允许/放行的意思,而代表抓取。假设该公司...

    2024-01-30 21:01:03
  • 浙江省高校招生计算机类职业,浙江高校招生职业技能考试大纲:计算机类

    (三)数字媒体技术应用技能操作考试模块1.掌握Photoshop工具箱中常用工具的操作(1)能利用选区工具组绘制选区;(2)能利用选区编辑工具及命令进行选区的编辑;(3)能利用修复工具组和图章工具组进...

    2024-01-30 21:00:55
  • springcloud gateway分发服务报302,Network Error

    springcloud gateway分发服务报302,Network Error

    springcloud使用gateway分发服务,访问接口时一直报错:打开浏览器控制台一直提示302,然后转发到login;多次试验后发现是pom.xml文件中引入的secret依赖的问题,注释后重新...

    2024-01-30 21:00:47
  • A-color-image-encryption-technique-using-exclusive-OR-with-DNA-complementary-rules-based-on-chaos...

    A-color-image-encryption-technique-using-exclusive-OR-with-DNA-complementary-rules-based-on-chaos...

    【论文地址】A-color-image-encryption-technique-using-exclusive-OR-with-DNA-complementary-rules-based-on-ch...

    2024-01-30 21:00:18