• 04 SecurityContextHolder与SecurityContext说明


    该篇记录一下SecurityContextHolder与SecurityContext两个类,当然还有与它们关系密码的SecurityContextPersistenceFilter.java这个过滤器

    1. SecurityContext.java

    查看spring security的源码,发现它就是个接口,spring security提供了一个默认的实现SecurityContextImpl.java. 仔细一看,该类其实就是对Authentication对象进行了封装,当然,覆写了equals和hashCode两个方法。

    public class SecurityContextImpl implements SecurityContext {
    
        private static final long serialVersionUID = SpringSecurityCoreVersion.SERIAL_VERSION_UID;
    
        // ~ Instance fields
        // ================================================================================================
    
        private Authentication authentication;
    
        public SecurityContextImpl() {}
    
        public SecurityContextImpl(Authentication authentication) {
            this.authentication = authentication;
        }
    
        // ~ Methods
        // ========================================================================================================
    
        @Override
        public boolean equals(Object obj) {
            if (obj instanceof SecurityContextImpl) {
                SecurityContextImpl test = (SecurityContextImpl) obj;
    
                if ((this.getAuthentication() == null) && (test.getAuthentication() == null)) {
                    return true;
                }
    
                if ((this.getAuthentication() != null) && (test.getAuthentication() != null)
                        && this.getAuthentication().equals(test.getAuthentication())) {
                    return true;
                }
            }
    
            return false;
        }
    
        @Override
        public Authentication getAuthentication() {
            return authentication;
        }
    
        @Override
        public int hashCode() {
            if (this.authentication == null) {
                return -1;
            }
            else {
                return this.authentication.hashCode();
            }
        }
    
        @Override
        public void setAuthentication(Authentication authentication) {
            this.authentication = authentication;
        }
    
    }

    2. SecurityContextHolder.java

    官方解释就是: Associates a given {@link SecurityContext} with the current execution thread.(与当前线程的securitycontext有关)

    其实,它就是存储SecurityContext对象。

    默认的策略采用ThreadLocal

    源代码如下:

    public class SecurityContextHolder {
        // ~ Static fields/initializers
        // =====================================================================================
    
        public static final String MODE_THREADLOCAL = "MODE_THREADLOCAL";
        public static final String MODE_INHERITABLETHREADLOCAL = "MODE_INHERITABLETHREADLOCAL";
        public static final String MODE_GLOBAL = "MODE_GLOBAL";
        public static final String SYSTEM_PROPERTY = "spring.security.strategy";
        private static String strategyName = System.getProperty(SYSTEM_PROPERTY);
        private static SecurityContextHolderStrategy strategy;
        private static int initializeCount = 0;
    
        static {
            initialize();
        }
    
    
        private static void initialize() {
            // 如果没有设置自定义的策略,就采用MODE_THREADLOCAL模式
            if (!StringUtils.hasText(strategyName)) {
                // Set default
                strategyName = MODE_THREADLOCAL;
            }
            // ThreadLocal策略
            if (strategyName.equals(MODE_THREADLOCAL)) {
                strategy = new ThreadLocalSecurityContextHolderStrategy();
            }
            // 采用InheritableThreadLocal,它是ThreadLocal的一个子类
            else if (strategyName.equals(MODE_INHERITABLETHREADLOCAL)) {
                strategy = new InheritableThreadLocalSecurityContextHolderStrategy();
            }
            // 全局策略,实现方式就是static SecurityContext contextHolder
            else if (strategyName.equals(MODE_GLOBAL)) {
                strategy = new GlobalSecurityContextHolderStrategy();
            }
            else {
                // 自定义的策略,通过返回创建出
                try {
                    Class<?> clazz = Class.forName(strategyName);
                    Constructor<?> customStrategy = clazz.getConstructor();
                    strategy = (SecurityContextHolderStrategy) customStrategy.newInstance();
                }
                catch (Exception ex) {
                    ReflectionUtils.handleReflectionException(ex);
                }
            }
    
            initializeCount++;
        }
    
    }

    补充说明: InheritableThreadLocal 与 ThreadLocal的区别

    ThreadLocal , 存储变量只能被当前线程使用

    InheritableThreadLocal  , 父线程中存储的变量子线程也可使用

    3. SecurityContextPersistenceFilter.java 

    该过滤器是spring security 过滤器链的第一个过滤器,所以请求进来时,第一个经过它,响应数据时,最后一个经过它。

    请求进来时, 它会检测session中是否有SecurityContext,如果有,它会将SecurityContext从session中拿出来,放到线程中。

    当请求响应时,它会检测线程是否有SecurityContext,如果有,它会将SecurityContext放到session中去。

    这样,不同的请求,就可以拿到同一个认证信息 Authentication

    下面看具体源码:

    public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
                throws IOException, ServletException {
            HttpServletRequest request = (HttpServletRequest) req;
            HttpServletResponse response = (HttpServletResponse) res;
    
            ......
            
            // 将request与response对象封装成一个HttpRequestResponseHolder对象,减少方法列表个数
            HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request,response);
            
            // 检测session中是否有SecurityContext,如果有就从session中获取,如果没有,创建一个新的
            SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
    
            try {
                // 将SecurityContext对象放到当前执行的线程中
                SecurityContextHolder.setContext(contextBeforeChainExecution);
                // 调用过滤器链
                chain.doFilter(holder.getRequest(), holder.getResponse());
    
            }
            finally {
                // 从当前线程中获取SecurityContext对象
                SecurityContext contextAfterChainExecution = SecurityContextHolder.getContext();
                // 清空当前线程中的SecurityContext
                SecurityContextHolder.clearContext();
                // 将SecurityContext放入到session中
                repo.saveContext(contextAfterChainExecution, holder.getRequest(),holder.getResponse());
            }
        }

    接着看下loadContext(holder)方法的源码

    public SecurityContext loadContext(HttpRequestResponseHolder requestResponseHolder) {
            HttpServletRequest request = requestResponseHolder.getRequest();
            HttpServletResponse response = requestResponseHolder.getResponse();
            // 获取session
            HttpSession httpSession = request.getSession(false);
            // 从session中获取SecurityContext对象
            SecurityContext context = readSecurityContextFromSession(httpSession);
    
            // 如果是null,就创建一个新的
            if (context == null) {
                context = generateNewContext();
    
            }
            .......
    
            return context;
        }

    4. 总结 

    这几个相关的类看完了,感叹spring的代码就是写得好!

    (1) SecurityContextHolder它的责任就是存储SecurityContext对象,但是怎么存储,采用何种存储策略则是通过SecurityContextHolderStrategy来实现的。SecurityContextHolderStrategy只是一个抽象接口,spring security 默认提供了几种存储策略,它们都实现了SecurityContextHolderStrategy接口。如果我们想自定义存储策略,肯定也得实现SecurityContextHolderStrategy。这样子,SecurityContextHolder 只需要提供存储策略的方式,至于如何实现这种存储策略,则完全交给了SecurityContextHolderStrategy及其实现类来控制,做到责任分离吧!

    (2) SecurityContextPersistenceFilter也是骚了一逼,将交量转换用得神了!

  • 相关阅读:
    介绍一个成功的 Git 分支模型 Release 分支
    启动安卓模拟器报错 emulator: ERROR: x86_64 emulation currently requires hardware acceleration! CPU acceleration status:HAXM must be updated(version 1.1.1<6.0.1) 解决办法
    AceyOffice教程复制行
    AceyOffice教程设置单元格边框
    Excel生成报表之解决方案合并单元格的用法
    AceyOffice教程报表之解决方案(二)
    Excel生成报表之解决方案插入图片
    AceyOffice教程复制列
    Excel基础知识(一)
    AceyOffice教程报表之解决方案(一)
  • 原文地址:https://www.cnblogs.com/z-qinfeng/p/11784466.html
Copyright © 2020-2023  润新知