springsecurity 源碼解讀之 SecurityContext
在springsecurity 中,我們一般可以通過代碼:
SecurityContext securityContext = SecurityContextHolder.getContext();
Authentication auth = securityContext.getAuthentication();
獲取當前登錄人員信息,其實我們可以從SecurityContext 獲取 springsecurity 實現的秘密。
就讓我從SecurityContextHolder 一步步抽絲剝繭,解讀一下SecurityContext 的來源。
這裏我們可以通過查看SecurityContextHolder 源碼,看看這個SecurityContext 到底 是哪裏來的。
public static void clearContext() { strategy.clearContext(); } /** * Obtain the current <code>SecurityContext</code>. * * @return the security context (never <code>null</code>) */ public static SecurityContext getContext() {return strategy.getContext(); } public static void setContext(SecurityContext context) { strategy.setContext(context); }
代碼很簡單,我們可以看到他是通過 strategy 獲取 的,那這個strategy 有是什麽東西呢?
通過跟蹤 源碼發現 ,這個 strategy 實際是ThreadLocalSecurityContextHolderStrategy 的一個實例對象。
那這個ThreadLocalSecurityContextHolderStrategy 又是什麽呢,我們繼續看源碼。
final class ThreadLocalSecurityContextHolderStrategy implements SecurityContextHolderStrategy { //~ Static fields/initializers ===================================================================================== private static final ThreadLocal<SecurityContext> contextHolder = new ThreadLocal<SecurityContext>(); //~ Methods ======================================================================================================== public void clearContext() { contextHolder.remove(); } public SecurityContext getContext() { SecurityContext ctx = contextHolder.get(); if (ctx == null) { ctx = createEmptyContext(); contextHolder.set(ctx); } return ctx; } public void setContext(SecurityContext context) { Assert.notNull(context, "Only non-null SecurityContext instances are permitted"); contextHolder.set(context); } public SecurityContext createEmptyContext() { return new SecurityContextImpl(); } }
不過就是一個放到一個線程變量中。看到這裏我們需要知道 什麽使用調用了這個setContext 方法。
通過跟蹤源碼發現:原來調用這個setContext方法的類:
SecurityContextPersistenceFilter
這是一個過濾器。我們知道 springsecurity 是有一組 過濾器 組成的,並且這個過濾器排在這些過濾器的第一個位置。
這個我們終於找到設置這個context的源頭了。
通過閱讀源碼 ,我們看到這樣兩行代碼.
HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); SecurityContext contextBeforeChainExecution = repo.loadContext(holder);
我們看到了這個SecurityContext 的源頭了。
我們找到了這個 SecurityContext 是通過這個SecurityContextRepository 類獲取的。
我們找到了這個接口的實現 HttpSessionSecurityContextRepository。
public SecurityContextPersistenceFilter() { this(new HttpSessionSecurityContextRepository()); } public SecurityContextPersistenceFilter(SecurityContextRepository repo) { this.repo = repo; }
最終我們找到了HttpSessionSecurityContextRepository代碼
private SecurityContext readSecurityContextFromSession(HttpSession httpSession) { final boolean debug = logger.isDebugEnabled(); if (httpSession == null) { if (debug) { logger.debug("No HttpSession currently exists"); } return null; } // Session exists, so try to obtain a context from it. Object contextFromSession = httpSession.getAttribute(springSecurityContextKey); if (contextFromSession == null) { if (debug) { logger.debug("HttpSession returned null object for SPRING_SECURITY_CONTEXT"); } return null; } // We now have the security context object from the session. if (!(contextFromSession instanceof SecurityContext)) { if (logger.isWarnEnabled()) { logger.warn(springSecurityContextKey + " did not contain a SecurityContext but contained: ‘" + contextFromSession + "‘; are you improperly modifying the HttpSession directly " + "(you should always use SecurityContextHolder) or using the HttpSession attribute " + "reserved for this class?"); } return null; } if (debug) { logger.debug("Obtained a valid SecurityContext from " + springSecurityContextKey + ": ‘" + contextFromSession + "‘"); } // Everything OK. The only non-null return from this method. return (SecurityContext) contextFromSession; }
這裏我們可以看到 實際 這個SecurityContext 實際是從session 中獲取的。
Object contextFromSession = httpSession.getAttribute("SPRING_SECURITY_CONTEXT");
從這裏我們就知道了這個SecurityContext 來源了。
步驟是:
1.通過 SecurityContextPersistenceFilter 這個過濾器,從session 中獲取SecurityContext .
2.並且把這個SecurityContext 放到線程變量中,然後我們在這個請求中就可以直接通過SecurityContextHolder.getContext();
獲取SecurityContext 對象了。
解決了獲取的問題,我們只需要把登錄後的SecurityContext 放到httpsession 中就好了。
下面代碼就是登錄的代碼實現:
UsernamePasswordAuthenticationToken authRequest = new UsernamePasswordAuthenticationToken(userName, pwd); authRequest.setDetails(new WebAuthenticationDetails(request)); SecurityContext securityContext = SecurityContextHolder.getContext(); Authentication auth = authenticationManager.authenticate(authRequest); securityContext.setAuthentication(auth); HttpSession session = request.getSession(); session.setAttribute(HttpSessionSecurityContextRepository.SPRING_SECURITY_CONTEXT_KEY, securityContext);
通過上面的代碼,我們就將securityContext 放到 session中了。
springsecurity 源碼解讀之 SecurityContext