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也是骚了一逼,将交量转换用得神了!

原文地址:https://www.cnblogs.com/z-qinfeng/p/11784466.html