SpringSecurity 原理解析【4】:令牌還原與Session
Session:通常稱爲會話,不一樣環境中含義不一樣,在Spring Security中一個會話指:客戶端從令牌認證請求到登出註銷請求之間的過程。Web應用基本都是基於HTTP協議,而該協議是一個無狀態協議,兩個HTTP請求相互獨立,沒法識別對方,在Web應用特別是Spring Security等安全框架中,同一個客戶端發出的多個請求,若是不能識別出,每一個請求都須要認證過程,這對於每次請求都須要主動提供身份識別信息的客戶端而言絕對是一個災難。所以須要一個方案來解決這個問題:解決方案總體而言只有一個:就給相關的HTTP請求添加一個全局總線,將這些HTTP請求打上統一標誌來講明這些請求都是來自同一個身份。而實現目前主流兩種:Session 和 WebToken,前者爲主流標準Web應用總線方案實現,標誌打在Cookie或者URL上,後者爲主流先後端分離Web應用總線方案實現,標誌打在請求頭上。html
今天主要說明前者Session。在JavaEE中Session特指HttpSession,其規範是在服務器端定義且實現的:默認每一個請求都會具備一個Session實例java
public interface HttpSession { // 建立時間 long getCreationTime(); boolean isNew(); // 惟一ID String getId(); // 最近一次訪問時間 long getLastAccessedTime(); //Servlet上下文 ServletContext getServletContext(); // 具備失效能力 void invalidate(); void setMaxInactiveInterval(int var1); int getMaxInactiveInterval(); // 具備存儲能力 Object getAttribute(String var1); Enumeration<String> getAttributeNames(); void setAttribute(String var1, Object var2); void removeAttribute(String var1); }
此時能夠這麼理解Session:是一個具備惟一標識且可控生命週期的存儲結構,底層通常爲:ConcurrentMap<String, Object>。web
Session追蹤
會話追蹤: SessionTracking,意思是追蹤Session來源,也就是從哪裏開始查找總線標記,默認爲Cookie和URL,@since Servlet 3.0可配置spring
public enum SessionTrackingMode { COOKIE, URL, SSL }
Cookie就是將sessionId寫入Cookie,URL則是在重定向時將sessionId寫入URL。而解析Cookie或者URL是在HttpServer內部完成的,例如:tomcat、undertow。因此通常看不到Session構建的細節(不一樣服務器實現不同),例如Tomcat服務器在構建Request時在CoyoteAdapter#postParseRequest,解析SessionId順序爲URL --> Cookie --> SSL ,且Cookie優先級最高,URL次之。後端
雖然不用知道如何解析Session,可是JavaEE給出構建入口和構建要求:HttpServletRequest#getSession,默認狀況下session的構建交給HttpServer完成,但對於分佈式應用,能夠由Spring Session模塊接手Session的生命週期。瀏覽器
注意若是會話追蹤ID丟失,則會致使Session==null,後續全部依賴了Session存儲的功能就會失敗:例如:CsrfFiltertomcat
// 主動肯定返回的Session是否須要從新構建 HttpSession getSession(boolean var1); // 獲取當前請求的Session實例,若是不存在則構建 HttpSession getSession(); // 修改SessionId String changeSessionId(); // Session有效性:是否存活 boolean isRequestedSessionIdValid(); // Session構建來源:是否從Cookie中解析 boolean isRequestedSessionIdFromCookie(); // Session構建來源:是否從URL中解析 boolean isRequestedSessionIdFromURL();
在Spring Security中在服務端完善令牌以後,能夠從上篇文章圖示中看到:令牌完整以後進行了Session、Context和Cookie管理.安全
Session的處理是經過SessionAuthenticationStrategy來執行的。默認是組合(Composite)策略,內置:ChangeSessionIdAuthenticationStrategy,複用現有Session,修改其惟一標識。服務器
CsrfAuthenticationStrategy (org.springframework.security.web.csrf) ConcurrentSessionControlAuthenticationStrategy (org.springframework.security.web.authentication.session) RegisterSessionAuthenticationStrategy (org.springframework.security.web.authentication.session) CompositeSessionAuthenticationStrategy (org.springframework.security.web.authentication.session) NullAuthenticatedSessionStrategy (org.springframework.security.web.authentication.session) AbstractSessionFixationProtectionStrategy (org.springframework.security.web.authentication.session) ChangeSessionIdAuthenticationStrategy (org.springframework.security.web.authentication.session) SessionFixationProtectionStrategy (org.springframework.security.web.authentication.session)
在Session作了案底以後就能夠在後續請求中獲取到並還原了cookie
令牌還原
在Spring Security中有一個優先級很高的過濾器:SecurityContextPersistenceFilter:上下文持久化過濾器,還記得在FilterSecurityInterceptor中獲取服務端完整令牌就是從SecurityContext中獲取的嗎?
SecurityContext contextBeforeChainExecution = securityContextRepository.loadContext(holder);
這裏有個SecurityContextRepository,安全上下文存儲庫,默認是HttpSessionSecurityContextRepository,也就是從HttpSession中獲取到的上下文。而HttpSession則在會話追蹤中已經還原了。
// -------------------------------- session 存儲 ------------------------------ // 先獲取到請求中的Session HttpSession httpSession = request.getSession(false); // 從Session中獲取SecurityContext SecurityContext context = readSecurityContextFromSession(httpSession); // context 就是從ConcurrentMap中 key="SPRING_SECURITY_CONTEXT"獲取 Object contextFromSession = httpSession.getAttribute(springSecurityContextKey); // -------------------------------- context 存儲 ------------------------------ SecurityContextHolder.setContext(context);
到這裏,Session的總體流程就清晰明瞭了,總體圖示以下:
Session配置
Session細節交給了服務器去設置,可是Session的配置接口是規範好的:
public interface SessionCookieConfig { // 名稱配置:常見爲:JSESSIONID void setName(String var1); String getName(); // 設置能攜帶Cookie的請求域名 // 後綴匹配,設置格式:點+域名 void setDomain(String var1); String getDomain(); // 設置能攜帶Cookie的請求路徑 // 前綴匹配:緊鄰域名以後的部分URL,默認:/ void setPath(String var1); String getPath(); // 設置額外備註 void setComment(String var1); String getComment(); // 是否容許客戶端操做Cookie void setHttpOnly(boolean var1); boolean isHttpOnly(); // 設置能攜帶Cookie的請求方式: // ture: https,false: http、https void setSecure(boolean var1); boolean isSecure(); // 有效期配置,默認-1,常見:3600 void setMaxAge(int var1); int getMaxAge(); }
Spring對Server的Session有對應的配置類,在容器啓動是會配置到Servlet中。示例:
server.servlet.context-path= /ctx server.servlet.session.cookie.name= Authorization server.servlet.session.cookie.path= /ctx/cookie/ server.servlet.session.cookie.max-age=3600 server.servlet.session.cookie.http-only=true server.servlet.session.cookie.secure=false server.servlet.session.cookie.comment=new cookie name
瀏覽器:
Set-Cookie: Authorization=_xkjjuKHOrOLbS3KRlUmOrRYYn-Z9oa-cLCLWS54; Version=1; Path=/ctx/cookie/; HttpOnly; Max-Age=3600; Expires=Mon, 26-Oct-2020 02:25:57 GMT; Comment="new cookie name"
後續攜帶Cookie
Cookie: Authorization=dhiWDzOksYItVwcqIZAew0YQqtA8BI9DZIVUXWjK
注意:Cookie的path默認爲:"/",意味着任何頁面若是攜帶Cookie,將是同一個。若是配置爲其餘值,必定要保證Spring Security的認證路徑能匹配到來傳輸Cookie。配置格式應該爲:"contextPath/匹配值路徑/",表示匹配路徑及其子路徑都會攜帶匹配路徑請求是產生的Cookie。