前言
本文主要整理一下SecurityContext的儲存方式。
SecurityContext介面
顧名思義,安全上下文。即儲存認證授權的相關資訊,實際上就是儲存"當前使用者"賬號資訊和相關許可權。這個介面只有兩個方法,Authentication物件的getter、setter。
package org.springframework.security.core.context; import java.io.Serializable; import org.springframework.security.core.Authentication; public interface SecurityContext extends Serializable { Authentication getAuthentication(); void setAuthentication(Authentication var1); }
Authentication介面又是幹嘛的?
注意:SecurityContext儲存的Authentication物件是經過認證的,所以它會帶有許可權,它的getAuthorities()方法會返回相關許可權。
package org.springframework.security.core; import java.io.Serializable; import java.security.Principal; import java.util.Collection; public interface Authentication extends Principal, Serializable { Collection<? extends GrantedAuthority> getAuthorities(); Object getCredentials(); Object getDetails(); Object getPrincipal(); boolean isAuthenticated(); void setAuthenticated(boolean var1) throws IllegalArgumentException; }
SecurityContextHolder工具類
前面說的"當前使用者"實際上指的是當前這個請求所對應的使用者,那麼怎麼知道當前使用者是誰呢?由於一個請求從開始到結束都由一個執行緒處理,這個執行緒中途也不會去處理其他的請求。所以在這段時間內,相當於這個執行緒跟當前使用者是一一對應的。SecurityContextHolder工具類就是把SecurityContext儲存在當前執行緒中。
SecurityContextHolder可以用來設定和獲取SecurityContext。它主要是給框架內部使用的,可以利用它獲取當前使用者的SecurityContext進行請求檢查,和訪問控制等。
在Web環境下,SecurityContextHolder是利用ThreadLocal來儲存SecurityContext的。
請求結束,SecurityContext儲存在哪裡?
我們知道Sevlet中執行緒是被池化複用的,一旦處理完當前的請求,它可能馬上就會被分配去處理其他的請求。而且也不能保證使用者下次的請求會被分配到同一個執行緒。所以存線上程裡面,請求一旦結束,就沒了。如果沒有儲存,不是每次請求都要重新認證登入?想想看,如果沒有許可權框架我們是怎麼處理的?
想到了吧,如果不用許可權框架,我們一般是把認證結果存在Session中的。同理,它也把認證結果儲存到Session了。
對應的Key是:"SPRING_SECURITY_CONTEXT"
流程檢視
SecurityContextPersistenceFilter攔截器
SecurityContextPersistenceFilter是Security的攔截器,而且是攔截鏈中的第一個攔截器,請求來臨時它會從HttpSession中把SecurityContext取出來,然後放入SecurityContextHolder。在所有攔截器都處理完成後,再把SecurityContext存入HttpSession,並清除SecurityContextHolder內的引用。
注:其中repo物件是HttpSessionSecurityContextRepository
public void doFilter(ServletRequest req, ServletResponse res, FilterChain chain) throws IOException, ServletException { HttpServletRequest request = (HttpServletRequest) req; HttpServletResponse response = (HttpServletResponse) res; if (request.getAttribute(FILTER_APPLIED) != null) { // ensure that filter is only applied once per request chain.doFilter(request, response); return; } final boolean debug = logger.isDebugEnabled(); request.setAttribute(FILTER_APPLIED, Boolean.TRUE); if (forceEagerSessionCreation) { HttpSession session = request.getSession(); if (debug && session.isNew()) { logger.debug("Eagerly created session: " + session.getId()); } } HttpRequestResponseHolder holder = new HttpRequestResponseHolder(request, response); //利用HttpSecurityContextRepository從HttpSesion中獲取SecurityContext物件 //如果沒有HttpSession,即瀏覽器第一次訪問伺服器,還沒有產生會話。 //它會建立一個空的SecurityContext物件 SecurityContext contextBeforeChainExecution = repo.loadContext(holder); try { //把SecurityContext放入到SecurityContextHolder中 SecurityContextHolder.setContext(contextBeforeChainExecution); //執行攔截鏈,這個鏈會逐層向下執行 chain.doFilter(holder.getRequest(), holder.getResponse()); } finally { //當攔截器都執行完的時候把當前執行緒對應的SecurityContext從SecurityContextHolder中取出來 SecurityContext contextAfterChainExecution = SecurityContextHolder .getContext(); // Crucial removal of SecurityContextHolder contents - do this before anything // else. SecurityContextHolder.clearContext(); //利用HttpSecurityContextRepository把SecurityContext寫入HttpSession repo.saveContext(contextAfterChainExecution, holder.getRequest(), holder.getResponse()); request.removeAttribute(FILTER_APPLIED); if (debug) { logger.debug("SecurityContextHolder now cleared, as request processing completed"); } } }
Tomcat建立會話的流程
有人可能對Tomcat建立會話的流程還不熟悉,這裡稍微整理一下。是這樣的,當客戶瀏覽器開啟後第一次訪問Tomcat伺服器,Tomcat會建立一個HttpSesion物件,存入一個ConcurrentHashMap,Key是SessionId,Value就是HttpSession。然後請求完成後,在返回的報文中新增Set-Cookie:JSESSIONID=xxx,然後客戶端瀏覽器會儲存這個Cookie。當瀏覽器再次訪問這個伺服器的時候,都會帶上這個Cookie。Tomcat接收到這個請求後,根據JSESSIONID把對應的HttpSession物件取出來,放入HttpSerlvetRequest物件裡面。
重點:
1.HttpSession會一直存在服務端,實際上是存在執行記憶體中。除非Session過期 OR Tomcat奔潰 OR 伺服器奔潰,否則會話資訊不會消失。
2.如無特殊處理,Cookie JSESSIONID會在瀏覽器關閉的時候清除。
3.Tomcat中HttpSesion的預設過期時間為30分鐘。
4.這些處理都在Security的攔截鏈之前完成。
——————————————————2019年1月21日更正——————————————
瀏覽器訪問Web專案並不會建立Session,只有在程式設計呼叫request.getSession()方法後Session才會建立,瀏覽器才會有JSESSIONID的Cookie。
一般是認證成功後,呼叫getSession()(其實在這之前Session並不存在),獲得Session物件,然後把資訊存裡面。
所以,一般的,在登入之前,你不管怎麼訪問網站,都不會建立會話。