分散式 session
分散式系統中,登入的 session 資訊一般都是存放在 redis 中的。
本文記錄一下 spring-boot 整合 redis 實現分散式 session 登入驗證。
快速開始準備工作本地啟動 redis 服務
[21496] 15 Sep 09:24:37.508 # oO0OoO0OoO0Oo Redis is starting oO0OoO0OoO0Oo[21496] 15 Sep 09:24:37.510 # Redis version=5.0.9, bits=64, commit=9414ab9b, modified=0, pid=21496, just started[21496] 15 Sep 09:24:37.510 # Configuration loaded[21496] 15 Sep 09:24:37.513 # Could not create server TCP listening socket 127.0.0.1:6379: bind: 操作成功完成。
進入客戶端
$ redis-cli.exe127.0.0.1:6379>
此時保證 redis 中資料是空的,或者沒有干擾資料:
127.0.0.1:6379> keys *(empty list or set)
基本程式碼pom.xml<dependencies> <dependency> <groupId>org.springframework.session</groupId> <artifactId>spring-session</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-data-redis</artifactId> </dependency></dependencies><build> <plugins> <plugin> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-maven-plugin</artifactId> </plugin> </plugins></build>
目錄結構D:.├─java│ └─com│ └─github│ └─houbb│ └─spring│ └─boot│ └─session│ │ Application.java│ ││ ├─config│ │ HttpSessionConfig.java│ ││ └─controller│ ExampleController.java│└─resources application.properties
後端程式碼啟動 session@EnableRedisHttpSessionpublic class HttpSessionConfig {}
Controller 示例程式碼
@RestControllerpublic class ExampleController { @RequestMapping("/set") public String set(HttpServletRequest req) { req.getSession().setAttribute("testKey", "testValue"); return "設定session:testKey=testValue"; } @RequestMapping("/query") public String query(HttpServletRequest req) { Object value = req.getSession().getAttribute("testKey"); return "查詢Session:\"testKey\"=" + value; }}
配置檔案主要指定 redis 的配置資訊。此處為本地 redis。
spring.redis.host=127.0.0.1spring.redis.password=spring.redis.port=6379
啟動測試設定
瀏覽器訪問 http://localhost:8080/set
頁面返回:
設定session:testKey=testValue
查詢
瀏覽器訪問 http://localhost:8080/query
頁面返回:
查詢Session:"testKey"=testValue
資訊的儲存
我們看一下 Redis 中的儲存資訊
127.0.0.1:6379> keys *1) "spring:session:sessions:d37d1c0a-5c4a-4c60-906b-7657be1e6bc7"2) "spring:session:expirations:1600135380000"3) "spring:session:sessions:expires:d37d1c0a-5c4a-4c60-906b-7657be1e6bc7"
這是 spring-session 為我們建立的 3 個 key
我們也可以看一下對應的值
spring:session:sessions:d37d1c0a-5c4a-4c60-906b-7657be1e6bc7127.0.0.1:6379> hgetall spring:session:sessions:d37d1c0a-5c4a-4c60-906b-7657be1e6bc71) "lastAccessedTime"2) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01t\x8fd_\xef"3) "maxInactiveInterval"4) "\xac\xed\x00\x05sr\x00\x11java.lang.Integer\x12\xe2\xa0\xa4\xf7\x81\x878\x02\x00\x01I\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\a\b"5) "sessionAttr:testKey"6) "\xac\xed\x00\x05t\x00\ttestValue"7) "creationTime"8) "\xac\xed\x00\x05sr\x00\x0ejava.lang.Long;\x8b\xe4\x90\xcc\x8f#\xdf\x02\x00\x01J\x00\x05valuexr\x00\x10java.lang.Number\x86\xac\x95\x1d\x0b\x94\xe0\x8b\x02\x00\x00xp\x00\x00\x01t\x8fd_\xef"
spring:session:expirations:1600135380000127.0.0.1:6379> smembers spring:session:expirations:16001353800001) "\xac\xed\x00\x05t\x00,expires:d37d1c0a-5c4a-4c60-906b-7657be1e6bc7"
spring:session:sessions:expires:d37d1c0a-5c4a-4c60-906b-7657be1e6bc7
127.0.0.1:6379> get spring:session:sessions:expires:d37d1c0a-5c4a-4c60-906b-7657be1e6bc7""
基於攔截器的處理
實際開發過程中,我們肯定不希望每一次請求都自己去實現 session 的校驗,查詢等處理。
關於這一點,可以直接交給 mvc 的攔截器實現。
核心程式碼實際上就是一個 servlet 的攔截器,每次請求獲取對應的 session 資訊。
這個可以基於 cookies/session/token 等等。
import org.springframework.util.StringUtils;import org.springframework.web.servlet.HandlerInterceptor;import org.springframework.web.servlet.ModelAndView;import javax.servlet.http.HttpServletRequest;import javax.servlet.http.HttpServletResponse;/** * @author binbin.hou * @since 1.0.0 */public class SessionInterceptor implements HandlerInterceptor { @Override public boolean preHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o) throws Exception { String token = httpServletRequest.getParameter("token"); String roleInfo = mockTokenResp(token); if(StringUtils.isEmpty(roleInfo)) { // 登入資訊非法,跳轉到登入頁面等操作 return false; } // 根據資訊設定等操作 return true; } /** * 根據 token 去 redis 等取 session 資訊,此處直接 mock 掉 * @param token 請求引數,可以是 sessionId, JWT 等 * @return 結果 */ private String mockTokenResp(String token) { if("ryo".equals(token)) { return "admin"; } return ""; } @Override public void postHandle(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, ModelAndView modelAndView) throws Exception { } @Override public void afterCompletion(HttpServletRequest httpServletRequest, HttpServletResponse httpServletResponse, Object o, Exception e) throws Exception { }}
小結session 是 web 登入中必備的功能,redis 存放 session 是分散式系統中比較成熟的方案。
當然也並不是唯一的解決方案,使用 jwt 也可以達到類似的效果,不過二者各有優缺點,個人更加傾向於使用 redis 儲存分散式 session。
後續有機會開啟一篇講解下 jwt 如何實現分散式系統的登入驗證。
本實戰系列用於記錄 springboot 的實際使用和學習筆記。
我是老馬,期待與你的下次相遇。
最新評論