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也是騷了一逼,將交量轉換用得神了!

 


免責聲明!

本站轉載的文章為個人學習借鑒使用,本站對版權不負任何法律責任。如果侵犯了您的隱私權益,請聯系本站郵箱yoyou2525@163.com刪除。



 
粵ICP備18138465號   © 2018-2025 CODEPRJ.COM