扩展篇:再探Spring Security过滤器链之SecurityContext

图1-1 思维导图

再探过滤器链

概述(一):SpringSecurity的前世今生 中我们曾提过Spring Security底层是通过一组过滤器链来实现的,当时简单的介绍了几个比较重要的过滤器;并且在后面的几篇认证文章中都有比较详细的讲解。

但前几篇的系列文章更多的是关注 认证本身,对一些细枝末节没有额外的展开;而本篇文章的目的是再探 Spring Security 的过滤器链,完整的剖析全链路。

经过 认证(一):基于表单登录的认证模式 的介绍,我们对认证的流程有了一个比较全面的认识;但是对于认证之后的事情呢?比如说:为什么我登录之后就不用再登录了呢?登录成功之后用户信息保存在哪里呢…… 别急,让我们先来看看完整的过滤器链以及认证链路全流程。

图1-2 过滤器链

从过滤器链图中我们发现,当我们在请求资源的时候,请求会先经过 SecurityContextPersistenceFilter过滤器,从其名字中我们大概能猜测出它应该是与某些资源的持久化(Persistence)有关。

图1-3认证链路全流程

认证链路全流程图大致上可以分两部分来理解,一部分是荧光绿的认证流程(在之前的认证系列中已经有过比较详细的将讲解);而另一部分是认证后的响应流程。

揭秘之旅

结合两张图示来看,未知的接口/类主要有:AuthenticationSecurityContextSecurityContextHolderSecurityContextPersistenceFilter、 那么就让我们一起来揭开它们神秘的面纱吧!

Authenticaiton

Authentication接口之前曾有过介绍,在认证成功之前存储的是客户端传递的登录信息,认证成功之后存储的是用户信息。

public interface Authentication extends Principal, Serializable {
    
    
    /**
     * 权限相关信息
     * 因SecurityContext存储的Authentication对象的经过认证的,所以会带有权限信息
     */
    Collection<? extends GrantedAuthority> getAuthorities();

    Object getCredentials();

    Object getDetails();

    Object getPrincipal();

    /**
     * 是否认证
     * 认证前存储的是客户端传递的登录信息,认证后存储的是用户信息
     */
    boolean isAuthenticated();

    void setAuthenticated(boolean isAuthenticated) throws IllegalArgumentException;
}

SecurityContext

SecurityContext用以存储认证授权的相关信息(已认证的Authentication),换句话说就是存储 当前用户的账号信息以及相关权限信息。

public interface SecurityContext extends Serializable {
    
    

    Authentication getAuthentication();

    void setAuthentication(Authentication authentication);
}

SecurityContextHolder

前面讲到 SecurityContext存储的是 当前用户的相关信息,那么如何判定当前用户是谁呢?实际上一个请求从开发到结束一般会是一个线程来处理,所以这段时间内这个 当前用户是和当前的处理线程是一一对应的。 SecurityContextHolder类的作用就是将 SecurityContext和当前线程所绑定,将其存储到当前线程中(实现方式是利用 ThreadLocal)。

SecurityContextHolder主要是用于框架内部使用,比如说利用它获取当前用户的 SecurityContext进行请求检查以及访问控制等。

这时候可能有朋友会有疑问:“既然是与当前线程所绑定,那么当一个请求结束后,用户信息不就消失了吗?那不是每次都会需要重新认证?”

答案在 SecurityContextPersistenceFilter中。

SecurityContextPersistenceFilter

SecurityContextPersistenceFilter是过滤器链中的第一个过滤器,它的主要工作是:当请求来临的时候,它会从 HttpSession中把对应用户的 SecurityContext放入到 SecurityContextHolder并且在所有拦截器都处理完毕之后,将含有当前用户信息的 SecurityContext存入 HttpSession中,并清除 SecurityContextHolder中的引用(释放 ThreadLoacal)。

public class SecurityContextPersistenceFilter extends GenericFilterBean {
    
    
    static final String FILTER_APPLIED = "__spring_security_scpf_applied";
    private SecurityContextRepository repo;
    private boolean forceEagerSessionCreation;

    public SecurityContextPersistenceFilter() {
    
    
        this(new HttpSessionSecurityContextRepository());
    }

    public SecurityContextPersistenceFilter(SecurityContextRepository repo) {
    
    
        this.forceEagerSessionCreation = false;
        this.repo = repo;
    }

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException {
    
    
        HttpServletRequest request = (HttpServletRequest)req;
        HttpServletResponse response = (HttpServletResponse)res;
        if (request.getAttribute("__spring_security_scpf_applied") != null) {
    
    
            chain.doFilter(request, response);
        } else {
    
    
            boolean debug = this.logger.isDebugEnabled();
            request.setAttribute("__spring_security_scpf_applied", Boolean.TRUE);
            if (this.forceEagerSessionCreation) {
    
    
                HttpSession session = request.getSession();
                if (debug && session.isNew()) {
    
    
                    this.logger.debug("Eagerly created session: " + session.getId());
                }
            }

            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response);
            // 从HttpSessionSecurityContextRepository中尝试获取SecurityContext对象,如果是还未登录的用户,则返回的是一个空的SecurityContext对象
            SecurityContext contextBeforeChainExecution = this.repo.loadContext(holder);
            boolean var13 = false;

            try {
    
    
                var13 = true;
                // 将securityContext设置到 SecurityContextHolder中
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                // 执行后续的拦截器链
                chain.doFilter(holder.getRequest(), holder.getResponse());
                var13 = false;
            } finally {
    
    
                if (var13) {
    
    
                    // 当后续的拦截器链执行完毕后,从SecurityContextHolder中取出 securityContext并做如下事情
                    SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                    // 1.回收SecurityContextHolder中的 securityContext引用(相当于释放ThreadLocal)
                    SecurityContextHolder.clearContext();
                    // 2.将 securityContext信息存储到HttpSession中
                    this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
                    request.removeAttribute("__spring_security_scpf_applied");
                    if (debug) {
    
    
                        this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
                    }

                }
            }

            SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
            SecurityContextHolder.clearContext();
            this.repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse());
            request.removeAttribute("__spring_security_scpf_applied");
            if (debug) {
    
    
                this.logger.debug("SecurityContextHolder now cleared, as request processing completed");
            }

        }
    }

    public void setForceEagerSessionCreation(boolean forceEagerSessionCreation) {
    
    
        this.forceEagerSessionCreation = forceEagerSessionCreation;
    }
}

总结

至此,我们总算对认证的全链路有了一个比较完整的认识:当请求到达服务端,首先会经过 SecurityContextPersistenceFilter拦截器,它会尝试从 HttpSession中获取安全上下文 SecurityContext并且将其设置到 SecurityContextHolder中,然后等过滤器链上的所有过滤器都执行完后,再将 SecurityContextHolder中的 安全上下文 SecurityContext清除(请求走到了最后阶段),并且将 SecurityContext设置到 HttpSession中。

HttpSession存在于服务端,默认的过期时间是30分钟(当然这些都是可以设置的),有兴趣的朋友可以自行了解一番~

本文为个人学习总结,如有错误之处请指出!

参考:Spring Security 之 SecurityContext

文章到这就结束拉,欢迎大家扫码关注小奇公众号~
请添加图片描述

猜你喜欢

转载自blog.csdn.net/weixin_46920376/article/details/109260982