首頁>技術>

分散式 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-7657be1e6bc7
127.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:1600135380000
127.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 的實際使用和學習筆記。

我是老馬,期待與你的下次相遇。

16
  • BSA-TRITC(10mg/ml) TRITC-BSA 牛血清白蛋白改性標記羅丹明
  • Spring Boot 的 2020最後一擊:2.4.1釋出