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

前端與Spring Boot后端無感Token 刷新的完整實例代碼

 更新時間:2025年06月26日 11:08:30   作者:Micro麥可樂  
本文詳細介紹無感Token刷新的核心原理,以及前端Axios攔截器與后端 Spring Boot + MyBatis-Plus + Redis 的完整示例代碼,通過雙 Token、Redis 校驗與攔截重試,你可以在保證安全性的同時,給用戶帶來 無感登錄過期刷新的體驗,感興趣的朋友跟隨小編一起看看吧

1. 前言

在我們前后端分離的應用中,常用的身份認證方案是基于 JWTJSON Web Token)。在保證安全性的同時,短生命周期的 Access Token 又會帶來頻繁登錄的體驗痛點。為了解決這個問題,我們引入 Refresh Token 并結合無感刷新機制,讓客戶端在 Access Token 過期時自動刷新,而無需用戶手動重新登錄,從而最大化提升用戶體驗。

小伙伴們可以通過本文,快速掌握無感 Token 刷新的原理以及實現(xiàn)方式

2. 為什么要無感刷新

在基于Token的用戶認證系統(tǒng)中,通常會設計兩種Token

Access Token:用于訪問資源,有效期短(通常15-30分鐘)
Refresh Token:用于獲取新Access Token,有效期長(通常7天)

傳統(tǒng)Token機制存在兩大痛點:

頻繁強制退出Access Token過期時用戶需重新登錄
安全隱患:延長Access Token有效期會增加安全風險

無感刷新解決了這些問題:

用戶體驗優(yōu)先
Access Token 常設很短(如 5–15 分鐘),若不自動刷新,登錄態(tài)會頻繁過期,用戶被迫“重新登錄”,體驗極差

安全與性能平衡
短生命周期的 Access Token 能減少被截獲濫用的風險
結合 Refresh Token(相對較長有效期),可以在安全與便捷間找到最佳點

前后端解耦
通過前端攔截器統(tǒng)一處理過期場景,無須在各業(yè)務請求中散落重復邏輯
后端專注提供刷新接口與失效策略,無需關心前端實現(xiàn)細節(jié)

3 無感刷新原理

3.1 無感刷新流程

3.2 關鍵技術點

雙 Token 機制

Access Token:短時有效,攜帶用戶身份和權限
Refresh Token:長期有效,專用于換取新的 Access Token

攔截與重試

1、前端在每次 API 請求中攜帶 Access Token;
2、若響應為 401 Unauthorized(或后端自定義過期碼),前端攔截器自動調用刷新token接口,用 Refresh Token 獲取新一對 Token;
3、獲取成功后,前端重新發(fā)起失敗的原始請求,用戶無感知。

后端安全策略
Refresh Token 寫入 Redis,并在刷新時做一次性或者滑動過期(可選)校驗;
Refresh Token 刷新后失效,防止被盜用。

4、前端實現(xiàn)

下面以 Axios 為例演示攔截器邏輯。我們將 Tokens 保存在 localStorage 或者更安全的 [HttpOnly Cookie] 中(此處示例用 localStorage 方便演示)

// auth.js
import axios from 'axios';
// Base Axios 實例
const api = axios.create({
  baseURL: '/api',
});
// Token 存取
function getAccessToken() { return localStorage.getItem('access_token'); }
function getRefreshToken() { return localStorage.getItem('refresh_token'); }
function setTokens({ accessToken, refreshToken }) {
  localStorage.setItem('access_token', accessToken);
  localStorage.setItem('refresh_token', refreshToken);
}
// 請求攔截:自動附帶 Access Token
api.interceptors.request.use(config => {
  const token = getAccessToken();
  if (token) config.headers['Authorization'] = `Bearer ${token}`;
  return config;
});
// 響應攔截:遇到 401 刷新并重試
let isRefreshing = false;
let subscribers = [];
function onRefreshed(newToken) {
  subscribers.forEach(cb => cb(newToken));
  subscribers = [];
}
function addSubscriber(cb) {
  subscribers.push(cb);
}
api.interceptors.response.use(
  res => res,
  error => {
    const { config, response } = error;
    if (response && response.status === 401 && !config._retry) {
      if (isRefreshing) {
        // 正在刷新,加入隊列
        return new Promise(resolve => {
          addSubscriber(token => {
            config.headers['Authorization'] = `Bearer ${token}`;
            resolve(api(config));
          });
        });
      }
      config._retry = true;
      isRefreshing = true;
      // 調用刷新接口
      return api.post('/auth/refresh', { refreshToken: getRefreshToken() })
        .then(res => {
          const { accessToken, refreshToken } = res.data;
          setTokens({ accessToken, refreshToken });
          isRefreshing = false;
          onRefreshed(accessToken);
          // 重試原請求
          config.headers['Authorization'] = `Bearer ${accessToken}`;
          return api(config);
        })
        .catch(err => {
          // 刷新失敗,跳轉登錄
          isRefreshing = false;
          window.location.href = '/login';
          return Promise.reject(err);
        });
    }
    return Promise.reject(error);
  }
);
export default api;

要點說明

isRefreshingsubscribers 用于解決多個并發(fā) 401 時只發(fā)送一次刷新請求;
_retry 標記避免無限循環(huán);
刷新失敗后,需清除本地登錄態(tài)并跳轉到登錄頁。

5. 后端實現(xiàn)

5.1 基礎依賴(pom.xml)

<dependencies>
  <!-- Spring Boot Starter Web -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>
  <!-- MyBatis-Plus -->
  <dependency>
    <groupId>com.baomidou</groupId>
    <artifactId>mybatis-plus-boot-starter</artifactId>
    <version>3.5.3.1</version>
  </dependency>
  <!-- MySQL 驅動 -->
  <dependency>
    <groupId>mysql</groupId>
    <artifactId>mysql-connector-java</artifactId>
  </dependency>
  <!-- Redis -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-data-redis</artifactId>
  </dependency>
  <!-- JWT -->
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-api</artifactId>
    <version>0.11.5</version>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-impl</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
  </dependency>
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt-jackson</artifactId>
    <version>0.11.5</version>
    <scope>runtime</scope>
  </dependency>
</dependencies>

5.2 數(shù)據庫與實體(存儲用戶可選)

這里就簡單模擬用戶,僅有用戶名和密碼為例

-- 用戶表(簡化)
CREATE TABLE user_account (
  id BIGINT PRIMARY KEY AUTO_INCREMENT,
  username VARCHAR(50) UNIQUE NOT NULL,
  password VARCHAR(255) NOT NULL
);

5.3 Redis 存儲 Refresh Token

我們用 ·Redis· 的 String,Key 為 refresh:{userId},Value 存 JSON { token, expireTime }

5.4 JWT 工具類

// JwtUtil.java
@Component
public class JwtUtil {
    @Value("${jwt.secret}") private String secret;
    @Value("${jwt.access.expire}") private long accessExpire;   // ms
    @Value("${jwt.refresh.expire}") private long refreshExpire; // ms
    // 生成 Access Token(短期)
    public String generateAccessToken(Long userId) {
        return Jwts.builder()
            .setSubject(userId.toString())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + accessExpire))
            .signWith(Keys.hmacShaKeyFor(secret.getBytes()))
            .compact();
    }
    // 生成 Refresh Token(長期)
    public String generateRefreshToken(Long userId) {
        return Jwts.builder()
            .setSubject(userId.toString())
            .setIssuedAt(new Date())
            .setExpiration(new Date(System.currentTimeMillis() + refreshExpire))
            .signWith(Keys.hmacShaKeyFor(secret.getBytes()))
            .compact();
    }
    // 解析 Token
    public Claims parseToken(String token) {
        return Jwts.parserBuilder()
            .setSigningKey(secret.getBytes())
            .build()
            .parseClaimsJws(token)
            .getBody();
    }
}

5.5 刷新服務

// AuthService.java
@Service
public class AuthService {
    @Autowired private JwtUtil jwtUtil;
    @Autowired private StringRedisTemplate redis;
    public Tokens login(String username, String password) {
        // 1. 驗證用戶名密碼(略,用 MyBatis-Plus 查詢)
        Long userId = /* ... */;
        // 2. 生成雙 Token
        String accessToken  = jwtUtil.generateAccessToken(userId);
        String refreshToken = jwtUtil.generateRefreshToken(userId);
        // 3. 保存到 Redis
        String key = "refresh:" + userId;
        redis.opsForValue().set(key, refreshToken, jwtUtil.getRefreshExpire(), TimeUnit.MILLISECONDS);
        return new Tokens(accessToken, refreshToken);
    }
    public Tokens refresh(String refreshToken) {
        // 1. 解析
        Claims claims = jwtUtil.parseToken(refreshToken);
        Long userId = Long.parseLong(claims.getSubject());
        // 2. Redis 校驗
        String key = "refresh:" + userId;
        String cached = redis.opsForValue().get(key);
        if (cached == null || !cached.equals(refreshToken)) {
            throw new RuntimeException("Refresh Token 無效或已過期");
        }
        // 3. 生成新 Token
        String newAccess  = jwtUtil.generateAccessToken(userId);
        String newRefresh = jwtUtil.generateRefreshToken(userId);
        // 4. 覆蓋 Redis
        redis.opsForValue().set(key, newRefresh, jwtUtil.getRefreshExpire(), TimeUnit.MILLISECONDS);
        return new Tokens(newAccess, newRefresh);
    }
}

5.6 控制器Controller

// AuthController.java
@RestController
@RequestMapping("/api/auth")
public class AuthController {
    @Autowired private AuthService authService;
    @PostMapping("/login")
    public Tokens login(@RequestBody LoginReq req) {
        return authService.login(req.getUsername(), req.getPassword());
    }
    @PostMapping("/refresh")
    public Tokens refresh(@RequestBody Map<String,String> body) {
        return authService.refresh(body.get("refreshToken"));
    }
}
// DTOs
@Data
class LoginReq { private String username, password; }
@Data
@AllArgsConstructor
class Tokens { 
    private String accessToken; 
    private String refreshToken; 
}

5.7 JWT 驗證過濾器

由于驗證并非本文的重點,小伙伴們可以參考博主的 《Spring Security》專欄學習,這里僅提供思路:
在每次請求攔截中,解析 Access Token 并將用戶信息放入 SecurityContext,若過期則交由前端刷新邏輯處理。

6. 結語

本文詳細介紹了 無感 Token 刷新 的核心原理,以及前端 Axios 攔截器與后端 Spring Boot + MyBatis-Plus + Redis 的完整示例代碼。通過雙 Token、Redis 校驗與攔截重試,你可以在保證安全性的同時,給用戶帶來 無感登錄過期刷新 的體驗

后續(xù)可繼續(xù)優(yōu)化:

  • Refresh Token 滑動過期:每次刷新延長有效期;
  • Refresh Token 一次性使用:每個舊 Token 只能刷新一次;
  • 前端多 tab 協(xié)調:同域下可共享刷新狀態(tài),避免重復刷新;
  • 安全加固:結合 IP、UA 風控,防止 Token 被盜用。

到此這篇關于前端與Spring Boot后端無感Token 刷新的完整實例代碼的文章就介紹到這了,更多相關Spring Boot無感Token刷新內容請搜索腳本之家以前的文章或繼續(xù)瀏覽下面的相關文章希望大家以后多多支持腳本之家!

相關文章

  • Java中Map與List作用、定義、調用實戰(zhàn)詳解

    Java中Map與List作用、定義、調用實戰(zhàn)詳解

    在Java編程語言中,數(shù)據結構之間轉換是常見的需求,尤其是集合框架中的List、Set和Map以及數(shù)組,這篇文章主要介紹了Java中Map與List作用、定義、調用的相關資料,需要的朋友可以參考下
    2025-12-12
  • SpringBoot實現(xiàn)任意文件在線預覽功能

    SpringBoot實現(xiàn)任意文件在線預覽功能

    在現(xiàn)代工作與生活環(huán)境中,文件共享與查看已經成為日常不可或缺的一部分,不過傳統(tǒng)的文件處理方式要求用戶下載文件到本地后才能查看,不僅占用了設備的存儲空間,還可能導致文件傳播效率低下,所以本文給大家介紹了SpringBoot實現(xiàn)任意文件在線預覽功能,需要的朋友可以參考下
    2025-07-07
  • Java中使用增強for循環(huán)的實例方法

    Java中使用增強for循環(huán)的實例方法

    在本篇文章里小編給大家整理是的關于Java中如何使用增強for循環(huán)的實例內容以及相關代碼,需要的朋友們可以學習下。
    2019-08-08
  • java中forward轉發(fā)的使用

    java中forward轉發(fā)的使用

    在Java中,forward轉發(fā)是一種非常常見且重要的操作,我們將深入探討forward的概念和用法,并給出一些代碼示例來幫助讀者更好地理解,感興趣的可以了解下
    2023-11-11
  • SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析

    SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析

    這篇文章主要介紹了SpringBoot 動態(tài)配置郵箱發(fā)件人過程解析,文中通過示例代碼介紹的非常詳細,對大家的學習或者工作具有一定的參考學習價值,需要的朋友可以參考下
    2019-08-08
  • 使用@Slf4j注解,log.info()無法使用問題

    使用@Slf4j注解,log.info()無法使用問題

    在使用Lombok的@Slf4j注解打印日志時遇到問題,通過降低Lombok版本(從1.18.x降至1.16.10)解決了問題
    2024-12-12
  • Spring?Aware接口示例代碼詳解

    Spring?Aware接口示例代碼詳解

    Spring的依賴注入的最大亮點是所有的Bean對Spring容器的存在是沒有意識的,我們可以將Spring容器換成其他的容器,Spring容器中的Bean的耦合度因此也是極低的,本文給大家介紹Spring?Aware接口示例代碼詳解,感興趣的朋友一起看看吧
    2022-02-02
  • java 算法之快速排序實現(xiàn)代碼

    java 算法之快速排序實現(xiàn)代碼

    這篇文章主要介紹了java 算法之快速排序實現(xiàn)代碼的相關資料,需要的朋友可以參考下
    2017-05-05
  • idea如何忽略.iml文件和.idea目錄

    idea如何忽略.iml文件和.idea目錄

    這篇文章主要介紹了idea如何忽略.iml文件和.idea目錄問題,具有很好的參考價值,希望對大家有所幫助,如有錯誤或未考慮完全的地方,望不吝賜教
    2023-12-12
  • javaWeb使用servlet搭建服務器入門

    javaWeb使用servlet搭建服務器入門

    這篇文章主要為大家詳細介紹了javaWeb使用servlet搭建服務器入門,具有一定的參考價值,感興趣的小伙伴們可以參考一下
    2017-11-11

最新評論

连南| 绵阳市| 朝阳区| 龙胜| 南汇区| 应城市| 公主岭市| 麻阳| 依兰县| 广南县| 大名县| 长宁区| 郎溪县| 梁河县| 土默特右旗| 布拖县| 昌宁县| 湟中县| 台湾省| 乌拉特中旗| 酒泉市| 班戈县| 鲁甸县| 万宁市| 灵山县| 中方县| 井冈山市| 南康市| 白城市| 阳谷县| 清新县| 佛冈县| 阿城市| 博乐市| 渭源县| 潮州市| 福鼎市| 鸡西市| 新沂市| 乌兰浩特市| 宜兴市|