摘要:會話管理一直是企業級應用的重要部分。傳統會話管理技術的問題的目的是解決傳統的會話管理技術的各種問題。對如和之類的閉源產品,找到適合它們的會話管理技術的替代實現則通常是不可能的。典型的應用會將當前用戶的身份及其安全級別或角色存儲在會話里面。
歡迎大家前往騰訊云+社區,獲取更多騰訊海量技術實踐干貨哦~
本文來自云+社區翻譯社,由Tnecesoc編譯。
會話管理一直是 Java 企業級應用的重要部分。不過在很長的一段時間里,這一部分都被我們認為是一個已解決的問題,并且也沒有什么重大的創新出現。
然而,微服務還有可橫向伸縮的云原生應用這一現代趨勢揭露了現今的會話管理技術在設計上的一些缺陷,挑戰著我們在過去 20 多年來對這一設計得出的一些結論。
本文會演示Spring Session API 為了幫助我們克服以前的會話管理方式的一些局限所采取的方法。我們將會先總結一下當前的會話管理技術的問題,然后深入探討 Spring Session 解決這些問題所采取的策略。最后,我們會總結 Spring Session 的工作方式以及在具體項目里面的一些用法。
Spring Session 為企業級 Java 應用的會話管理領域帶來了革新,讓我們可以輕松做到:
編寫可橫向伸縮的云原生應用
將會話狀態的存儲外放到專門的外部會話存儲里,比如 Redis 或 Apache Geode,后者以獨立于應用程序服務器的方式提供了高質量的存儲集群
在用戶通過 WebSocket 發出請求的時候保持 HttpSession 的在線狀態
訪問來自非 Web 請求處理指令的會話數據,比如 JMS 消息處理指令
為每個瀏覽器建立多個會話提供支持,從而構建更豐富的終端用戶體驗
控制在客戶端和服務器間交換會話 ID 的方式,從而編寫在 HTTP 報文首部中提取會話 ID 而脫離對 Cookie 的依賴的 RESTul API
注意,Spring Session 項目其實并不依賴于 Spring 框架,因此我們甚至能在不使用 Spring 框架的項目里面用到它。
傳統會話管理技術的問題Spring Session 的目的是解決傳統的 JavaEE 會話管理技術的各種問題。下面就通過一些例子說明一些這方面的問題。
構建可橫向伸縮的云原生應用程序從云原生應用架構的視角來看,一個應用應該可以通過在一個大型的虛擬機池里運行更多的 Linux 容器來部署更多的實例的方式來得到橫向的伸縮。比如,我們能很輕松地將一個這樣的應用的 war 文件部署到 Cloud Foundry 或 Heroku 上的 Tomcat 里面,然后在幾秒內擴展出 100 個應用程序實例,使得其中每個實例都有 1GB 的 RAM。我們還可以將云平臺設置成會根據用戶需求自動增減應用程序實例的數量。
很多應用都會把 HTTP 會話狀態存儲在運行應用代碼的 JVM 里面。這很容易實現,而且存取的速度也很快。當一個應用實例加入或退出集群的時候,HTTP 會話的存儲會在所有尚存的應用程序實例上重新進行平均的分配。在彈性云環境中,我們會運行數以百計的應用實例,且實例數量可能隨時發生快速的增減變化。這就帶來了一些問題:
HTTP 會話存儲的重新分配會成為性能瓶頸;
存儲大量會話所需的堆空間太大,會導致垃圾回收過程頻繁進行,并影響性能;
TCP 組播通常會被云端的基礎架構所禁止,但會話管理器需要經常用它來發現加入或退出集群的應用實例。
因此,將 HTTP 會話狀態存儲在運行應用代碼的 JVM 之外的數據存儲中會更高效。例如可以設置并使用 Redis 來存儲上述的 100 個 Tomcat 實例里面的會話狀態,那么 Tomcat 實例數量的增減便不會影響到在 Redis 中的會話存儲的模式。另外,因為 Redis 是用 C 語言編寫的,所以它可以在沒有垃圾回收機制影響其運行的前提下,動用數百 GB 甚至 TB 數量級的內存。
對像 Tomcat 這樣的開源服務器,找到使用外部數據存儲(如 Redis 或 Memcached)的會話管理技術的其他實現是很簡單的,但是使用起來的配置過程可能很復雜,并且每個應用服務器的配置過程可能都不一樣。對如 WebSphere 和 Weblogic 之類的閉源產品,找到適合它們的會話管理技術的替代實現則通常是不可能的。
Spring Session 為設置插件式的會話數據存儲提供了一種獨立于具體應用服務器的方法,使得我們能在 Servlet 框架的范疇內實現這樣的存儲,而不用依賴于具體的應用服務器的 API。這意味著 Spring Session 可以與所有實現了 Servlet 規范的應用服務器(Tomcat,Jetty,WebSphere,WebLogic,JBoss)協同工作,并在所有應用服務器上以完全相同且很容易的方式來進行配置。
我們還可以根據我們的需求選用最適合的外部會話數據存儲。這使得 Spring Session 也成了一個能幫助我們將傳統的 JavaEE 應用遷移到云端并作為一個符合十二要素的應用的一個理想的遷移工具。
一個用戶,多個賬戶假設你正在 example.com 上運行一個面向大眾的 Web 應用,其中一些人類用戶創建了多個帳號。例如,用戶 Jeff Lebowski 可能有兩個帳號 thedude@example.com 和 lebowski@example.com。跟其他 Java Web 應用程序一樣,你可以使用 HttpSession 來跟蹤各種會話狀態,比如當前登錄的用戶。因此,當用戶想從 dude@example.com 切換到 lebowski@example.com 時,就必須注銷當前賬號并重新登錄。
使用 Spring Session 來為每個用戶配置多個 HTTP 會話就很簡單了。這時 Jeff Lebowski 無需注銷和登錄就可以在 thedude@example.com 和 lebowski@example.com 之間來回切換。
不同安全級別下的預覽想象一下,你要構建一個具有復雜的自定義授權體系的 Web 應用,其中具有不同權限的用戶會具有不同的應用 UI 樣式。
比如說,假設應用有四個安全級別:公開(public)、保密(confidential)、機密(secret)以及絕密(top secret)。在用戶登錄到應用時,系統會識別這一用戶的安全級別,然后只對其顯示不高于其安全級別的數據。這樣,公開級別的用戶可以看到公開級別的文檔;具有保密級別的用戶能看公開和保密級別的,以此類推。為了讓用戶界面更加友好,我們的應用也應該能讓用戶預覽應用的 UI 在較低的安全級別下的樣子。比如絕密級別用戶應該能在秘密模式下預覽應用的各項事物的外觀。
典型的 Web 應用會將當前用戶的身份及其安全級別或角色存儲在 HTTP 會話里面。不過,由于 Web 應用的每個用戶只有一個會話,因此也只能通過注銷再登錄的方式來切換用戶的角色,或者實現一個用戶多個會話這一形式。
憑借 Spring Session,我們就可以很輕松地給每個登錄用戶創建多個相互獨立的會話,預覽功能的實現也會因此變得簡單。比如當前以絕密等級登錄的用戶想要預覽機密等級下的應用時,就可以對其創建并使用一個新的安全級別為機密的會話。
在使用 Web Sockets 時保持登錄狀態再想象一個場景,在用戶通過 example.com 登錄到我們的 Web 應用時,他們能使用通過 Websockets 工作的一個 HTML5 即時聊天客戶端進行對話。不過,根據 Servlet 規范,通過 Websockets 發出的請求不會更新會話的過期時間,因此在用戶進行聊天的時候,無論他們的聊天有多頻繁,會話也可能聊著聊著就沒了,然后 Websocket 連接也會因此關閉,聊天也就無法繼續了。
又是憑借 Spring Session,我們可以很輕松地確保 Websocket 請求還有常規的 HTTP 請求都能更新會話的過期時間。
訪問對非 Web 請求的會話數據再想象一下,我們的應用提供了兩種訪問方式,一個基于 HTTP 的 RESTful API,另一個是基于 RabbitMQ 的 AMQP 消息。此時,執行處理 AMQP 消息的的線程是無法訪問應用服務器的 HttpSession 的,對此我們必須自己寫一個解決方案來訪問 HTTP 會話里邊的數據。
還是憑借 Spring Session,只要我們知道會話的 ID,就可以從應用程序的任意線程訪問 Spring Session。Spring Session 比以往的 Servlet HTTP 會話管理器有著功能更加豐富的 API,使得我們只需要知道會話 ID 就能定位我們想要找的會話。比如,我們可以用傳入消息的用戶標識字段來直接找到對應的會話。
Spring Session 的工作方式現在傳統應用服務器在 HTTP 會話管理方面的局限性已經在不同情境中展示過了,我們再來看看 Spring Session 是如何解決這些問題的。
Spring Session 架構在實現一個會話管理器的時候,有兩個關鍵問題必須得到解決:
如何創建一個高效、可靠、高可用的會話數據存儲集群?
如何確定能夠哪個會話的實例與哪個傳入的請求(形式有 HTTP、WebSocket、AMQP 等)相關聯?
不過在本質上,有個更關鍵的問題是:如何跨越不同的請求協議來傳輸一個會話的 ID?
第一個問題對 Spring Session 來說已被各種高可用可伸縮的集群存儲(Redis、Gemfire、Apache Geode 等)很好地解決了。因此 Spring Session 也應該定義一組標準接口來使得對底層數據存儲的訪問可以用不同的數據存儲來實現。Spring Session 在定義 Session 和 ExpiringSession 這些基本的關鍵接口之外,也針對了不同數據存儲的訪問定義了關鍵接口 SessionRepository。
org.springframework.session.Session 是定義會話基本功能的接口,例如屬性的設置和刪除。這個接口并不依賴于具體的底層技術,因此可以比 Servlet 里面的 HttpSession 適用于更多的情況;
org.springframework.session.ExpiringSession 則擴展了 Session 接口。它提供了一些屬性,讓我們可以設置具有時效性的會話,并查詢這個會話是否已經過期。RedisSession 便是這個接口的一個實現范例。
org.springframework.session.SessionRepository 定義了創建,保存,刪除和查找會話的方法。將 Session 保存到數據存儲的實際邏輯便寫在這一接口的具體實現中。例如 RedisOperationsSessionRepository 便是這個接口的一個實現,它使用 Redis 來實現了會話的創建、保存以及刪除。
至于將請求關聯到特定會話實例的問題,Spring Session 則假定這一關聯的過程取決于特定的協議,因為客戶端和服務器在請求 / 響應周期期間就需要對所傳輸的會話 ID 達成一致。比如,如果客戶端發來一個 HTTP 請求,那么會話就可以通過 Cookie 或者 HTTP 報文首部來和請求相關聯。如果發來一個 HTTPS 請求,則可用 SSL 的 Session ID 字段來講會話與請求相關聯。若發來的是 JMS 消息,那也可以用消息首部來存儲請求和響應間的會話 ID。
對 HTTP 協議的關聯操作,Spring 會話定義了一個 HttpSessionStrategy 接口,后者有將 Cookies 和會話關聯在一起的 CookieHttpSessionStrategy 和使用了自定義報文首部字段來管理會話的 HeaderHttpSessionStrategy 兩種實現。
下面便詳細地介紹一下 Spring Session 在 HTTP 協議上的工作方式。
在本文發布時(2015.11.10),Spring Session 1.0.2 在當前的 GA 發行版提供了使用 Redis 的 Spring Session 的一套實現,以及支持任何分布式的 Map(如 Hazelcast)的實現。其實,實現 Spring Session 針對某種數據存儲的支持是相對容易的,在開源社區里已經有了很多這樣的實現。
基于 HTTP 的 Spring Session基于 HTTP 的 Spring Session 是以一個標準 Servlet 過濾器(filter)的形式實現的。這一過濾器應該截取所有的對 Web 應用的請求,并且也應該在諸多過濾器組成的鏈中排在第一個。Spring Session 的過濾器會負責確保所有后續的代碼里面對 javax.servlet.http.HttpServletRequest.getSession() 方法的調用都會呈遞給一個 Spinrg Session 的 HttpSession 實例,而不是應用服務器默認提供的 HttpSession。
要理解這點,最簡單的方法就是查閱 Spring Session 的實際源碼。我們首先從用來實現 Spring Session 的標準 Servlet 擴展點(extension points)開始。
在 2001 年,Servlet 2.3 規范引入了 ServletRequestWrapper。該類的 Javadoc 稱 ServletRequestWrapper “為 ServletRequest 接口能讓開發者繼承它來適配一種特別的 Servlet 提供了一種便利的實現。該類采用了包裝器,或者說裝飾器模式。對該類的 ServletRequest 類的方法的調用會被傳至其封裝的一個請求對象里去?!?下面這段從 Tomcat 里抽出來的代碼就展示了 ServletRequestWrapper 的實現方式。
public class ServletRequestWrapper implements ServletRequest { private ServletRequest request; /** * Creates a ServletRequest adaptor wrapping the given request object. * 創建一個裝有給定的請求對象的 ServletRequest 適配器 * @throws java.lang.IllegalArgumentException if the request is null * 如果請求對象為空就會拋出空指針異常 */ public ServletRequestWrapper(ServletRequest request) { if (request == null) { throw new IllegalArgumentException("Request cannot be null"); } this.request = request; } public ServletRequest getRequest() { return this.request; } public Object getAttribute(String name) { return this.request.getAttribute(name); } // 為可讀性著想, 接下來的代碼就略了 }
Servlt 2.3 規范還對 ServletRequestWrapper 定義了一個子類 HttpServletRequestWrapper。我們可以用它來快速地實現一個自定義的 HttpServletRequest。下面這段從 Tomcat 里抽出來的代碼就展示了 HttpServletRequestWrapper 這個類的實現方式。
public class HttpServletRequestWrapper extends ServletRequestWrapper implements HttpServletRequest { public HttpServletRequestWrapper(HttpServletRequest request) { super(request); } private HttpServletRequest _getHttpServletRequest() { return (HttpServletRequest) super.getRequest(); } public HttpSession getSession(boolean create) { return this._getHttpServletRequest().getSession(create); } public HttpSession getSession() { return this._getHttpServletRequest().getSession(); } // 為可讀性著想,接下來的代碼就略了 }
因此,我們就可以用這些包裝類來編寫一些擴展 HttpServletRequest 功能的代碼,重載返回 HttpSession 的方法,使得后者返回的是我們存儲在外部存儲倉庫里面的會話。這里就給出一份從 Spring Session 項目提出來的源碼就對應了這里提到的東西。為了能對應這里的解釋,源碼里面原本的注釋被我重寫了一下,在此不妨也看一看里面的注釋。
/* * Spring Session 項目定義了一個繼承了標準 HttpServletRequestWrapper 的類 * 它重載了 HttpServletRequest 里面的所有跟會話有關的方法 */ private final class SessionRepositoryRequestWrapper extends HttpServletRequestWrapper { private HttpSessionWrapper currentSession; private Boolean requestedSessionIdValid; private boolean requestedSessionInvalidated; private final HttpServletResponse response; private final ServletContext servletContext; /* * 構造方法這塊非常簡單 * 它會接收并設置一些之后會用到的參數, * 然后完成對 HttpServletRequestWrapper 的代理 */ private SessionRepositoryRequestWrapper( HttpServletRequest request, HttpServletResponse response, ServletContext servletContext) { super(request); this.response = response; this.servletContext = servletContext; } /* * Spring Session 便在這里用自己對返回存儲于外部數據源的會話數據的實現 * 取代了對應用服務器提供的默認方法的代理調用. * * 這里的實現會先檢查它是不是已經有一個對應的會話. * 若有那就返回之, 否則就會檢查當前的請求附帶的會話 ID 是否確實對應著一個會話 * 若有, 那就用這個會話 ID 從 SessionRepository 里邊加載這個會話; * 若外部數據源里沒這個會話, 或者這個會話 ID 沒對應的會話, * 那就創建一個新的會話, 并把它存在會話數據存儲里面. */ @Override public HttpSession getSession(boolean create) { if(currentSession != null) { return currentSession; } String requestedSessionId = getRequestedSessionId(); if(requestedSessionId != null) { S session = sessionRepository.getSession(requestedSessionId); if(session != null) { this.requestedSessionIdValid = true; currentSession = new HttpSessionWrapper(session, getServletContext()); currentSession.setNew(false); return currentSession; } } if(!create) { return null; } S session = sessionRepository.createSession(); currentSession = new HttpSessionWrapper(session, getServletContext()); return currentSession; } @Override public HttpSession getSession() { return getSession(true); } }
Spring Session 同時定義了一個 ServletFilter 接口的實現類 SessionRepositoryFilter。這里也會給出這個過濾器的實現的核心部分的源碼,并且也會附上一些對應本文內容的注釋,不妨也看一看。
/* * SessionRepositoryFilter 是一個標準 ServletFilter 的實現. * 其目的是從它的基類擴展出一些功能來. */ public class SessionRepositoryFilter < S extends ExpiringSession > extends OncePerRequestFilter { /* * 這一方法就是核心部分. * 該方法會創建一個我們在上面介紹過的包裝請求的實例, * 然后拿這個包裝過的請求再過一遍過濾器鏈的剩余部分. * 關鍵的地方在于,應用在執行位于這個過濾器之后的代碼時, * 如果要獲取會話的數據, 那這個包裝過的請求就會返回 Spring Session * 所保存在外部數據源的 HttpServletSession 實例. */ protected void doFilterInternal( HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { request.setAttribute(SESSION_REPOSITORY_ATTR, sessionRepository); SessionRepositoryRequestWrapper wrappedRequest = new SessionRepositoryRequestWrapper(request,response,servletContext); SessionRepositoryResponseWrapper wrappedResponse = new SessionRepositoryResponseWrapper(wrappedRequest, response); HttpServletRequest strategyRequest = httpSessionStrategy.wrapRequest(wrappedRequest, wrappedResponse); HttpServletResponse strategyResponse = httpSessionStrategy.wrapResponse(wrappedRequest, wrappedResponse); try { filterChain.doFilter(strategyRequest, strategyResponse); } finally { wrappedRequest.commitSession(); } } }
這一節的重點在于,基于 HTTP 的 Spring Session 其實也只是一個用了 Servlet 規范的標準特性來實現功能的經典的 Servlet 過濾器而已。因此,將現有的 Web 應用的 war 文件改成使用 Spring Session 是應該可以不用改動已有代碼的。然而,在應用里面用了 javax.servlet.http.HttpSessionListener 的情況則是例外。Spring Session 1.0 并沒有對 HttpSessionListener 提供支持,不過 Spring Session 1.1 M1 版本則對其添加了支持。詳情見此。
Spring Session 的設置在 Web 項目里面,Spring Session 的設置分為四步:
設置在 Spring Session 中使用的數據存儲
將 Spring Session 的 .jar 文件添加到 Web 應用中
將 Spring Session 的過濾器添加到 Web 應用的配置中
設置從 Spring Session 到所選會話數據存儲的連接
Spring Session 內置了對 Redis 的支持。安裝和設置 redis 的詳細信息見此。
完成上述 Spring Session 的設置步驟的常見方式有兩種。一種是使用 Spring Boot 來自動設置 Spring Session。另外一種則是手動完成每一個配置步驟。
用 Maven 和 Gradle 等依賴管理工具可以很輕松地將 Spring Session 加入到應用的依賴項目里面。比如說,如果你用的是 Spring Boot + Maven,那么就可以在 pom.xml 里面加上以下依賴項目:
org.springframework.session spring-session 1.0.2.RELEASE org.springframework.boot spring-boot-starter-redis
spring-boot-starter-redis 這一依賴項目會確保跟 redis 交互所需的 jar 包都包含在應用里面,這樣便可以使用 Spring Boot 來進行自動的配置。至于 spring-session 這一依賴項目則對應 Spring Session 的 jar 包。
設置 Spring Session Servlet 過濾器的過程可以通過 Spring Boot 自動完成,只需要在 Spring Boot 的配置類里面加上 @EnableRedisHttpSession 注解即可。就跟下面這段代碼一樣:
@SpringBootApplication @EnableRedisHttpSession public class ExampleApplication { public static void main(String[] args) { SpringApplication.run(ExampleApplication.class, args); } }
將下面這些配置信息加到 Spring Boot 的 application.properties 文件即可設置 Spring Session 到 Redis 的連接。
spring.redis.host=localhost spring.redis.password=secret spring.redis.port=6379
為了設置和 Redis 的連接,Spring Boot 提供了一套詳實的底層架構,使得我們可以在其中任意設置一種跟 Redis 建立連接的方式。你能在 Spring Session 還有 Spring Boot 里面找到按部就班進行的指南。
使用 web.xml 來設置傳統的 Web 應用去使用 Spring Session 的教程見此。
設置傳統的不帶有 web.xml 的 war 文件去使用 Spring Session 的教程見此。
在默認情況下,Spring Session 會使用 HTTP cookie 來存儲會話 ID,但是我們也可以將 Spring Session 設置成使用自定義的 HTTP 報文首部字段(例如 x-auth-token: 0dc1f6e1-c7f1-41ac-8ce2-32b6b3e57aa3
)來存儲會話 ID,而這在構建 RESTful API 的時候會非常有用。完整教程見此。
Spring Session 的用法在配置了 Spring Session 之后,我們就可以使用標準的 Servlet API 去和它進行交互了。比如下面這段代碼就定義了一個使用標準 Servlet 會話 API 來訪問會話數據的 servlet。
@WebServlet("/example") public class Example extends HttpServlet { @Override protected void doGet(HttpServletRequest request, HttpServletResponse response) throws ServletException, IOException { // 使用標準的 servlet API 去獲取對應的會話數據 // 這一會話數據就是 Spring Session 存在 Redis // 或是別的我們所指定的數據源里面的會話數據 HttpSession session = request.getSession(); String value = session.getAttribute(a€?someAttributea€?); } }一個瀏覽器,多個會話
Spring Session 通過使用一個叫做 _s 的會話代號參數來跟蹤每個用戶的多個會話。假如有個傳入請求的 URL 是 http://example.com/doSomething?_s=0,那么 Spring Session 就會讀取 _s 參數的值,然后便會認為這個請求對應的是默認的會話。
如果傳入請求的 URL 是 http://example.com/doSomething?_s=1,那么 Spring Session 就會知道這個請求對應的會話的代號是 1。如果傳入請求沒有指定參數 _s,那么 Spring Session 就會把它視為對應默認對話(即 _s = 0)。
為了讓每個瀏覽器都創建一個新的會話,我們只需像以前那樣調用 javax.servlet.http.HttpServletRequest.getSession(),然后 Spring Session 就會返回對應的會話,或者使用 Servlet 規范的語義創建一個新的會話。下表便給出了 getSession() 方法在同一瀏覽器的不同的 URL 參數下的具體表現形式:
HTTP 請求 URL | 會話代號 | getSession() 的具體表現 |
---|---|---|
example.com/resource | 0 | 如果存在與代號 0 相關聯的會話就返回之,否則就創建一個新會話,然后將其與代號 0 關聯起來 |
example.com/resource?_s=1 | 1 | 如果存在與代號 1 相關聯的會話就返回之,否則就創建一個新會話,然后將其與代號 1 關聯起來 |
example.com/resource?_s=0 | 0 | 如果存在與代號 0 相關聯的會話就返回之,否則就創建一個新會話,然后將其與代號 0 關聯起來 |
example.com/resource?_s=abc | abc | 如果存在與代號 abc 相關聯的會話就返回之,否則就創建一個新會話,然后將其與代號 abc 關聯起來 |
如上表所示,會話代號并不局限于整數,只要與發布給該用戶的所有其他會話別名不同,即可對一個一個新的會話。然而,整數類型的會話代號應該是最易用的,并且 Spring Session 也給出了 HttpSessionManager 來提供一些處理會話代號的實用方法。
我們可以通過 "org.springframework.session.web.HttpSessionManager" 這個屬性名來查找相應屬性,進而訪問到 HttpSessionManager。下面這段代碼就演示了獲得 HttpSessionManager 的引用的方法,以及這個實用方法類的一些主要的方法。
@WebServlet("/example") public class Example extends HttpServlet { @Override protected void doGet(HttpServletRequest request,HttpServletResponse response) throws ServletException, IOException { /* * 通過使用 "org.springframework.session.web.http.HttpSessionManager" * 這一屬性名在請求屬性中查找屬性 * 來獲取一個 Spring Session 的 HttpSessionManager 的引用 */ HttpSessionManager sessionManager=(HttpSessionManager)request.getAttribute( "org.springframework.session.web.http.HttpSessionManager"); /* * 用 HttpSessionManager 來找出 HTTP 請求所對應的會話代號. * 默認情況下這個會話代號會由 HTTP 請求的 URL 參數 _s 給出。 * 比如 http://localhost:8080/example?_s=1 這個 URL * 就會讓這里的 println() 方法打印 "Requested Session Alias is: 1" */ String requestedSessionAlias=sessionManager.getCurrentSessionAlias(request); System.out.println("Requested Session Alias is: " + requestedSessionAlias); /* * 返回一個當前還沒被瀏覽器用在請求參數里的唯一的會話代號. * 注意這一方法并不會創建一個新的會話, * 創建新的會話還是要通過 request.getSession() 來進行. */ String newSessionAlias = sessionManager.getNewSessionAlias(request); /* * 使用剛剛得到的新會話代號構造一個 URL, * 使其含有 _s 這個參數. * 比如若 newSessionAlias 的值是 2, * 那么這個方法就會返回 "/inbox?_s=3" */ String encodedURL = sessionManager.encodeURL("/inbox", newSessionAlias); System.out.println(encodedURL); /* * 返回一個會話代號為鍵, 會話 ID 為值的 Map, * 以便識別瀏覽器發來的請求所對應的會話. */ Map結論sessionIds = sessionManager.getSessionIds(request); } }
Spring Session 為企業級 Java 應用的會話管理領域帶來了革新,讓我們可以輕松做到:
編寫可橫向伸縮的云原生應用
將會話狀態的存儲外放到專門的外部會話存儲里,比如 Redis 或 Apache Geode,后者以獨立于應用程序服務器的方式提供了高質量的存儲集群
在用戶通過 WebSocket 發出請求的時候保持 HttpSession 的在線狀態
訪問來自非 Web 請求處理指令的會話數據,比如 JMS 消息處理指令
為每個瀏覽器建立多個會話提供支持,從而構建更豐富的終端用戶體驗
控制在客戶端和服務器間交換會話 ID 的方式,從而編寫在 HTTP 報文首部中提取會話 ID 而脫離對 Cookie 的依賴的 RESTul API
若你在尋找一種從傳統又笨重的應用服務器中解放的方法,但又囿于對應用服務器的會話存儲集群功能的依賴,那么 Spring Session 對像 Tomcat、Jetty 還有 Undertow 這樣的容器的輕量化來說是很好的一個選擇。
參考資料Spring Session 項目
Spring Session 教程及指南
HttpSession & Redis
Spring Boot 集成
Spring Security 集成
Restful APIs
用戶多重賬號
Web Socket集成
Websocket / HttpSession 超時交互
ASF Bugzilla - Bug 54738
WEBSOCKET SPEC-175
網絡研討會:Spring Session 導論
問答
傳統Web應用程序和API中的身份驗證、授權和會話管理如何實現?
相關閱讀
架構設計之Spring Session分布式集群會話管理?
Spring Session關鍵類源碼分析
一個可以把web表單變成會話形式的開源框架
此文已由作者授權騰訊云+社區發布,原文鏈接:https://cloud.tencent.com/dev...
歡迎大家前往騰訊云+社區或關注云加社區微信公眾號(QcloudCommunity),第一時間獲取更多海量技術實踐干貨哦~
文章版權歸作者所有,未經允許請勿轉載,若此文章存在違規行為,您可以聯系管理員刪除。
轉載請注明本文地址:http://specialneedsforspecialkids.com/yun/71410.html
摘要:從源碼的角度分析源碼分析從哪一步作為入口呢如果是看過我之前寫的那幾篇關于的源碼分析,我相信你不會在源碼前磨磨蹭蹭,遲遲找不到入口。 微信公眾號「后端進階」,專注后端技術分享:Java、Golang、WEB框架、分布式中間件、服務治理等等。 老司機傾囊相授,帶你一路進階,來不及解釋了快上車! 坐在我旁邊的鐘同學聽說我精通Mybatis源碼(我就想不通,是誰透漏了風聲),就順帶問了我一個...
摘要:大家好,今天給大家分享一個權限管理的框架的,說實話本來我是準備看的,畢竟是家族的框架,和整合更加容易一些。官方給出的介紹是是一個強大且易用的安全框架執行身份驗證授權密碼學和會話管理。由此可知,的主要功能是認證授權加密密和會話管理。 showImg(https://segmentfault.com/img/bV1BsT?w=1726&h=256); 大家好,今天給大家分享一個權限管理的框...
摘要:大家好,今天給大家分享一個權限管理的框架的,說實話本來我是準備看的,畢竟是家族的框架,和整合更加容易一些。官方給出的介紹是是一個強大且易用的安全框架執行身份驗證授權密碼學和會話管理。由此可知,的主要功能是認證授權加密密和會話管理。 showImg(https://segmentfault.com/img/bV1BsT?w=1726&h=256); 大家好,今天給大家分享一個權限管理的框...
閱讀 3735·2023-01-11 11:02
閱讀 4244·2023-01-11 11:02
閱讀 3050·2023-01-11 11:02
閱讀 5180·2023-01-11 11:02
閱讀 4737·2023-01-11 11:02
閱讀 5534·2023-01-11 11:02
閱讀 5313·2023-01-11 11:02
閱讀 3990·2023-01-11 11:02