最新国产好看的视频,伊人天堂AV在线,国产Aaaaaa视频,蜜臀视频在线观看一区,人妻av色图,密臀久久久精品影片,青青视频免费观看毛片,久草在线观看视,国产三级精品色情在线

SpringBoot基于Redis+Token實(shí)現(xiàn)分布式登錄系統(tǒng)

 更新時(shí)間:2026年06月07日 08:38:28   作者:ShaneD771  
本文主要介紹了SpringBoot基于Redis+Token實(shí)現(xiàn)分布式登錄系統(tǒng),基于Redis的Token無狀態(tài)登錄方案設(shè)計(jì),包括憑證機(jī)制、數(shù)據(jù)存儲于狀態(tài)管理,并并強(qiáng)調(diào)了高性能、無感刷新與職責(zé)單一等優(yōu)勢,下面就來詳細(xì)的介紹一下

1. 設(shè)計(jì)思路

在分布式系統(tǒng)或前后端分離的架構(gòu)中,我們需要一種無狀態(tài)的登錄方案。核心設(shè)計(jì)思路如下:

  1. 憑證機(jī)制:使用 Token(隨機(jī)字符串)作為用戶身份的唯一標(biāo)識,替代傳統(tǒng)的 Cookie。
  2. 數(shù)據(jù)存儲:將用戶的登錄狀態(tài)(Token 與用戶信息的映射)存儲在 Redis 中,利用其高性能和自動(dòng)過期機(jī)制。
  3. 狀態(tài)管理
    • Redis:作為服務(wù)端共享的“會話存儲中心”。
    • ThreadLocal:作為單次請求內(nèi)的“上下文容器”,方便 Controller 和 Service 層獲取用戶信息。
  4. 請求攔截:采用 雙攔截器模式,分離“Token 刷新”與“登錄鑒權(quán)”的職責(zé)。

2. 代碼實(shí)現(xiàn)

2.1 步驟一:發(fā)送短信驗(yàn)證碼

驗(yàn)證碼需要有短期的有效期(如 2 分鐘),適合使用 Redis 的 String 結(jié)構(gòu)存儲。

  • Key: login:code:{手機(jī)號}
  • Value: 驗(yàn)證碼數(shù)字
@Override
public Result sendCode(String phone) {
    // 1. 校驗(yàn)手機(jī)號格式
    if (RegexUtils.isPhoneInvalid(phone)) {
        return Result.fail("手機(jī)號格式錯(cuò)誤");
    }

    // 2. 生成 6 位隨機(jī)驗(yàn)證碼
    String code = RandomUtil.randomNumbers(6);

    // 3. 保存驗(yàn)證碼到 Redis
    // 設(shè)置 2 分鐘過期
    stringRedisTemplate.opsForValue().set(RedisConstants.LOGIN_CODE_KEY + phone, code, 2, TimeUnit.MINUTES);

    // 4. 模擬發(fā)送短信(實(shí)際項(xiàng)目中調(diào)用短信服務(wù)商 API)
    log.debug("發(fā)送驗(yàn)證碼成功,驗(yàn)證碼:{}", code);
    return Result.ok();
}

2.2 步驟二:登錄并生成 Token

登錄成功后,我們需要生成 Token,并將用戶對象存儲為 Redis 的 Hash 結(jié)構(gòu)。Hash 結(jié)構(gòu)適合存儲對象,且內(nèi)存占用更優(yōu)。

  • Key: login:token:{隨機(jī)Token}
  • Value: User 對象的字段映射 (Hash)
@Override
public Result login(LoginFormDTO loginForm) {
    String phone = loginForm.getPhone();

    // 1. 從 Redis 獲取驗(yàn)證碼并校驗(yàn)
    String cacheCode = stringRedisTemplate.opsForValue().get(RedisConstants.LOGIN_CODE_KEY + phone);
    String code = loginForm.getCode();
    if (cacheCode == null || !cacheCode.equals(code)) {
        return Result.fail("驗(yàn)證碼錯(cuò)誤");
    }

    // 2. 根據(jù)手機(jī)號查詢用戶(若不存在則注冊,代碼略)
    User user = query().eq("phone", phone).one();
    if (user == null) {
        user = createUserWithPhone(phone);
    }

    // 3. 生成隨機(jī) Token (作為登錄令牌)
    String token = UUID.randomUUID().toString(true);

    // 4. 將 User 對象轉(zhuǎn)為 HashMap 存儲
    // 核心細(xì)節(jié):StringRedisTemplate 要求 Value 必須是 String,
    // 所以必須將 Long 類型的 ID 強(qiáng)轉(zhuǎn)為 String,否則會報(bào) ClassCastException
    UserDTO userDTO = BeanUtil.copyProperties(user, UserDTO.class);
    Map<String, Object> userMap = BeanUtil.beanToMap(userDTO, new HashMap<>(),
            CopyOptions.create()
                    .setIgnoreNullValue(true)
                    .setFieldValueEditor((fieldName, fieldValue) -> fieldValue.toString()));

    // 5. 存儲到 Redis
    String tokenKey = RedisConstants.LOGIN_USER_KEY + token;
    stringRedisTemplate.opsForHash().putAll(tokenKey, userMap);
    
    // 6. 設(shè)置有效期(例如 30 分鐘)
    stringRedisTemplate.expire(tokenKey, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

    // 7. 返回 Token 給前端
    return Result.ok(token);
}
Redis 數(shù)據(jù)庫
│
├── Key: "login:token:xyz-123"  <-- 這是你設(shè)置的 Redis Key (Token)
│   │
│   └── Value (Hash結(jié)構(gòu))       <-- 這是一個(gè)大容器
│       ├── Field: "id"        --> Value: "101"   (必須是 String)
│       ├── Field: "nickName"  --> Value: "小明"  (必須是 String)
│       └── Field: "icon"      --> Value: "/a.jpg" (必須是 String)

2.3 步驟三:構(gòu)建“雙攔截器”防御體系

為了實(shí)現(xiàn) “用戶只要在使用 App,登錄狀態(tài)就一直有效” 的無感刷新體驗(yàn),我們將攔截器拆分為兩層。

第一層:Token 刷新攔截器 (RefreshTokenInterceptor)

  • 攔截路徑:所有路徑 (/**)
  • 職責(zé):只要攜帶了 Token,就刷新 Redis 有效期,并保存用戶信息到 ThreadLocal不負(fù)責(zé)攔截請求。
public class RefreshTokenInterceptor implements HandlerInterceptor {

    private StringRedisTemplate stringRedisTemplate;

    // 構(gòu)造函數(shù)注入 StringRedisTemplate
    public RefreshTokenInterceptor(StringRedisTemplate stringRedisTemplate) {
        this.stringRedisTemplate = stringRedisTemplate;
    }

    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 獲取請求頭中的 token
        String token = request.getHeader("authorization");
        if (StrUtil.isBlank(token)) {
            return true; // 沒 Token 也放行,交給下一道攔截器
        }

        // 2. 基于 token 獲取 Redis 中的用戶
        String key = RedisConstants.LOGIN_USER_KEY + token;
        Map<Object, Object> userMap = stringRedisTemplate.opsForHash().entries(key);

        // 3. 判斷用戶是否存在
        if (userMap.isEmpty()) {
            return true; // Token 無效或過期,也放行
        }

        // 4. 將查詢到的 Map 轉(zhuǎn)為 UserDTO 對象
        UserDTO userDTO = BeanUtil.fillBeanWithMap(userMap, new UserDTO(), false);

        // 5. 保存到 ThreadLocal (供后續(xù)業(yè)務(wù)使用)
        UserHolder.saveUser(userDTO);

        // 6. 【核心動(dòng)作】刷新 Token 有效期
        stringRedisTemplate.expire(key, RedisConstants.LOGIN_USER_TTL, TimeUnit.MINUTES);

        // 7. 放行
        return true;
    }

    @Override
    public void afterCompletion(HttpServletRequest request, HttpServletResponse response, Object handler, Exception ex) throws Exception {
        // 請求結(jié)束,清理 ThreadLocal,防止內(nèi)存泄漏
        UserHolder.removeUser();
    }
}

第二層:登錄攔截器 (LoginInterceptor)

  • 攔截路徑:敏感業(yè)務(wù)路徑 (排除首頁、登錄接口等)
  • 職責(zé):只檢查 ThreadLocal 是否有用戶。如果沒有,則攔截并返回 401。
public class LoginInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request, HttpServletResponse response, Object handler) throws Exception {
        // 1. 判斷 ThreadLocal 中是否有用戶
        if (UserHolder.getUser() == null) {
            // 2. 沒有用戶,攔截并返回 401 (未授權(quán))
            response.setStatus(401);
            return false;
        }
        // 3. 有用戶,放行
        return true;
    }
}

2.4 步驟四:配置攔截器 (MvcConfig)

在配置類中注冊這兩個(gè)攔截器,并通過 order 控制執(zhí)行順序:先刷新,后攔截。

@Configuration
public class MvcConfig implements WebMvcConfigurer {

    @Resource
    private StringRedisTemplate stringRedisTemplate;

    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        // 1. 注冊刷新攔截器 (Order = 0, 最先執(zhí)行)
        // 攔截所有請求,確保用戶訪問任何頁面都能刷新 Token
        registry.addInterceptor(new RefreshTokenInterceptor(stringRedisTemplate))
                .addPathPatterns("/**")
                .order(0);

        // 2. 注冊登錄攔截器 (Order = 1, 后執(zhí)行)
        // 只攔截需要保護(hù)的接口
        registry.addInterceptor(new LoginInterceptor())
                .excludePathPatterns(
                        "/user/login",
                        "/user/code",
                        "/blog/hot",
                        "/shop/**",
                        "/shop-type/**",
                        "/upload/**",
                        "/voucher/**"
                ).order(1);
    }
}

3. 業(yè)務(wù)層使用

在 Controller 或 Service 層,無需再操作 Request 或 Redis,直接從 ThreadLocal 獲取當(dāng)前登錄用戶即可。

@GetMapping("/me")
public Result me(){
    // 直接獲取當(dāng)前用戶
    UserDTO user = UserHolder.getUser();
    return Result.ok(user);
}

4. 總結(jié)

這套 Redis + Token + 雙攔截器 的方案具有以下優(yōu)勢:

  1. 高性能:Redis 的讀寫速度極快,不會拖慢請求響應(yīng)。
  2. 無感刷新:通過 RefreshTokenInterceptor,解決了用戶在使用過程4中 Token 突然過期的問題。
  3. 職責(zé)單一:登錄邏輯只負(fù)責(zé)生成 Token,攔截器只負(fù)責(zé)鑒權(quán),業(yè)務(wù)層只負(fù)責(zé)業(yè)務(wù),代碼結(jié)構(gòu)清晰,耦合度低。

到此這篇關(guān)于SpringBoot基于Redis+Token實(shí)現(xiàn)分布式登錄系統(tǒng)的文章就介紹到這了,更多相關(guān)SpringBoot 分布式登錄內(nèi)容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關(guān)文章希望大家以后多多支持腳本之家!

相關(guān)文章

  • java使用DOM4J對XML文件進(jìn)行增刪改查操作

    java使用DOM4J對XML文件進(jìn)行增刪改查操作

    這篇文章主要為大家詳細(xì)介紹了java使用DOM4J對XML文件進(jìn)行增刪改查操作,具有一定的參考價(jià)值,感興趣的小伙伴們可以參考一下
    2018-12-12
  • SpringBoot整合Shiro兩種方式(總結(jié))

    SpringBoot整合Shiro兩種方式(總結(jié))

    這篇文章主要介紹了SpringBoot整合Shiro兩種方式,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友們下面隨著小編來一起學(xué)習(xí)學(xué)習(xí)吧
    2019-06-06
  • 淺談log4j 不打印異常堆棧

    淺談log4j 不打印異常堆棧

    這篇文章主要介紹了淺談log4j 不打印異常堆棧,小編覺得還是挺不錯(cuò)的,具有一定借鑒價(jià)值,需要的朋友可以參考下
    2018-02-02
  • java非遞歸實(shí)現(xiàn)之二叉樹的前中后序遍歷詳解

    java非遞歸實(shí)現(xiàn)之二叉樹的前中后序遍歷詳解

    樹的遍歷順序大體分為三種:前序遍歷(先根遍歷、先序遍歷),中序遍歷(中根遍歷),后序遍歷(后根遍歷),本文將給大家詳細(xì)的介紹,對大家的學(xué)習(xí)或工作具有一定的參考借鑒價(jià)值
    2021-09-09
  • Ubuntu環(huán)境下的 RabbitMQ 安裝與配置詳細(xì)指南

    Ubuntu環(huán)境下的 RabbitMQ 安裝與配置詳細(xì)指南

    本文詳解Ubuntu下RabbitMQ安裝與配置,涵蓋Erlang依賴安裝、服務(wù)部署、管理界面啟用及安全用戶權(quán)限設(shè)置,強(qiáng)調(diào)多協(xié)議支持、高可用性設(shè)計(jì)和分布式場景適配,助力構(gòu)建穩(wěn)定可靠的消息隊(duì)列系統(tǒng),感興趣的朋友跟隨小編一起看看吧
    2025-09-09
  • SpringMVC工作原理實(shí)例詳解

    SpringMVC工作原理實(shí)例詳解

    這篇文章主要介紹了SpringMVC工作原理實(shí)例詳解,文中通過示例代碼介紹的非常詳細(xì),對大家的學(xué)習(xí)或者工作具有一定的參考學(xué)習(xí)價(jià)值,需要的朋友可以參考下
    2020-03-03
  • SpringBoot實(shí)現(xiàn)全局異常處理方法總結(jié)

    SpringBoot實(shí)現(xiàn)全局異常處理方法總結(jié)

    在項(xiàng)目開發(fā)中出現(xiàn)異常時(shí)很平常不過的事情,我們處理異常也有很多種方式。本文將詳細(xì)為大家講解SpringBoot實(shí)現(xiàn)全局異常處理幾種方法,感興趣的可以學(xué)習(xí)一下
    2022-03-03
  • springcloud gateway設(shè)置context-path的操作

    springcloud gateway設(shè)置context-path的操作

    這篇文章主要介紹了springcloud gateway設(shè)置context-path的操作,具有很好的參考價(jià)值,希望對大家有所幫助。如有錯(cuò)誤或未考慮完全的地方,望不吝賜教
    2021-07-07
  • SpringBoot中RestTemplate的使用詳解

    SpringBoot中RestTemplate的使用詳解

    這篇文章主要介紹了SpringBoot中RestTemplate的使用詳解,RestTemplate是由Spring框架提供的一個(gè)可用于應(yīng)用中調(diào)用rest服務(wù)的類它簡化了與http服務(wù)的通信方式,統(tǒng)一了RESTFul的標(biāo)準(zhǔn),封裝了http連接,我們只需要傳入url及其返回值類型即可,需要的朋友可以參考下
    2023-10-10
  • java常用的定時(shí)任務(wù)框架及相關(guān)代碼應(yīng)用示例

    java常用的定時(shí)任務(wù)框架及相關(guān)代碼應(yīng)用示例

    在Java開發(fā)中,定時(shí)任務(wù)是一個(gè)常見的需求,比如定時(shí)備份數(shù)據(jù)、定時(shí)清理日志、定時(shí)發(fā)送郵件等,這篇文章主要介紹了java常用的定時(shí)任務(wù)框架及相關(guān)代碼應(yīng)用的相關(guān)資料,文中通過代碼介紹的非常詳細(xì),需要的朋友可以參考下
    2025-11-11

最新評論

梧州市| 金堂县| 镇赉县| 壤塘县| 东乡县| 南丹县| 隆回县| 山东省| 庄浪县| 罗定市| 曲阜市| 芮城县| 正阳县| 临城县| 类乌齐县| 惠水县| 成都市| 马公市| 襄樊市| 庆阳市| 富阳市| 彝良县| 新巴尔虎左旗| 昌黎县| 拜泉县| 龙泉市| 大新县| 阳春市| 柞水县| 江山市| 临海市| 前郭尔| 无极县| 犍为县| 嘉黎县| 三门县| 平潭县| 成武县| 凤阳县| 湾仔区| 贵南县|