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