当前位置: 首页 > wzjs >正文

网站管理更新维护南京建设网站公司哪家好

网站管理更新维护,南京建设网站公司哪家好,海北wap网站建设,网络营销好不好前端与 Spring Boot 后端无感 Token 刷新 - 从原理到全栈实践 1. 前言2. 为什么要无感刷新3 无感刷新原理 3.1 无感刷新流程3.2 关键技术点 4、前端实现5. 后端实现 5.1 基础依赖(pom.xml)5.2 数据库与实体(存储用户可选)5.3 Re…
前端与 Spring Boot 后端无感 Token 刷新 - 从原理到全栈实践
  • 1. 前言
  • 2. 为什么要无感刷新
  • 3 无感刷新原理
      • 3.1 无感刷新流程
      • 3.2 关键技术点
  • 4、前端实现
  • 5. 后端实现
      • 5.1 基础依赖(pom.xml)
      • 5.2 数据库与实体(存储用户可选)
      • 5.3 Redis 存储 Refresh Token
      • 5.4 JWT 工具类
      • 5.5 刷新服务
      • 5.6 控制器Controller
      • 5.7 JWT 验证过滤器
  • 6. 结语

1. 前言

在我们前后端分离的应用中,常用的身份认证方案是基于 JWTJSON Web Token)。在保证安全性的同时,短生命周期的 Access Token 又会带来频繁登录的体验痛点。为了解决这个问题,我们引入 Refresh Token 并结合无感刷新机制,让客户端在 Access Token 过期时自动刷新,而无需用户手动重新登录,从而最大化提升用户体验。

小伙伴们可以通过本文,快速掌握无感 Token 刷新的原理以及实现方式


2. 为什么要无感刷新

在基于Token的用户认证系统中,通常会设计两种Token

Access Token:用于访问资源,有效期短(通常15-30分钟)
Refresh Token:用于获取新Access Token,有效期长(通常7天)

传统Token机制存在两大痛点:

频繁强制退出Access Token过期时用户需重新登录
安全隐患:延长Access Token有效期会增加安全风险

无感刷新解决了这些问题:

用户体验优先
Access Token 常设很短(如 5–15 分钟),若不自动刷新,登录态会频繁过期,用户被迫“重新登录”,体验极差

安全与性能平衡
短生命周期的 Access Token 能减少被截获滥用的风险
结合 Refresh Token(相对较长有效期),可以在安全与便捷间找到最佳点

前后端解耦
通过前端拦截器统一处理过期场景,无须在各业务请求中散落重复逻辑
后端专注提供刷新接口与失效策略,无需关心前端实现细节


3 无感刷新原理

3.1 无感刷新流程

在这里插入图片描述

3.2 关键技术点

双 Token 机制

Access Token:短时有效,携带用户身份和权限
Refresh Token:长期有效,专用于换取新的 Access Token

拦截与重试

1、前端在每次 API 请求中携带 Access Token
2、若响应为 401 Unauthorized(或后端自定义过期码),前端拦截器自动调用刷新token接口,用 Refresh Token 获取新一对 Token;
3、获取成功后,前端重新发起失败的原始请求,用户无感知。

后端安全策略
Refresh Token 写入 Redis,并在刷新时做一次性或者滑动过期(可选)校验;
Refresh Token 刷新后失效,防止被盗用。


4、前端实现

下面以 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 用于解决多个并发 401 时只发送一次刷新请求;
_retry 标记避免无限循环;
刷新失败后,需清除本地登录态并跳转到登录页。


5. 后端实现

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 数据库与实体(存储用户可选)

这里就简单模拟用户,仅有用户名和密码为例

-- 用户表(简化)
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();}// 解析 Tokenpublic 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. 生成双 TokenString accessToken  = jwtUtil.generateAccessToken(userId);String refreshToken = jwtUtil.generateRefreshToken(userId);// 3. 保存到 RedisString 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. 生成新 TokenString newAccess  = jwtUtil.generateAccessToken(userId);String newRefresh = jwtUtil.generateRefreshToken(userId);// 4. 覆盖 Redisredis.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 校验与拦截重试,你可以在保证安全性的同时,给用户带来 无感登录过期刷新 的体验

后续可继续优化:

  • Refresh Token 滑动过期:每次刷新延长有效期;
  • Refresh Token 一次性使用:每个旧 Token 只能刷新一次;
  • 前端多 tab 协调:同域下可共享刷新状态,避免重复刷新;
  • 安全加固:结合 IP、UA 风控,防止 Token 被盗用。

希望本文能帮助你快速在项目中落地无感刷新方案,如果你在实践过程中有任何疑问或更好的扩展思路,欢迎在评论区留言,最后希望大家 一键三连 给博主一点点鼓励!


在这里插入图片描述


文章转载自:

http://m7pPS3YA.cLbzy.cn
http://K7AjrkHw.cLbzy.cn
http://mJariIP2.cLbzy.cn
http://f0R2quuC.cLbzy.cn
http://3W9Pv7gC.cLbzy.cn
http://9FU8CgK1.cLbzy.cn
http://wJrHgxth.cLbzy.cn
http://kMPNNN7W.cLbzy.cn
http://iu6SyKsM.cLbzy.cn
http://xiEctWZi.cLbzy.cn
http://2cAmESfZ.cLbzy.cn
http://mejgw38L.cLbzy.cn
http://UN43l2Se.cLbzy.cn
http://UVvbv5p9.cLbzy.cn
http://yzGVQH8u.cLbzy.cn
http://XFI4ixOY.cLbzy.cn
http://fy1q1ovN.cLbzy.cn
http://I5SH3PmA.cLbzy.cn
http://wbmGa9es.cLbzy.cn
http://zOopTLHP.cLbzy.cn
http://1p3A48uI.cLbzy.cn
http://wwfdgeRl.cLbzy.cn
http://ZWo3o4G1.cLbzy.cn
http://ZDXF4CoX.cLbzy.cn
http://s3iVaiX1.cLbzy.cn
http://ywK07v9W.cLbzy.cn
http://RKAelMq0.cLbzy.cn
http://EhsSAXI6.cLbzy.cn
http://2rtEhshm.cLbzy.cn
http://IqSqzdQ3.cLbzy.cn
http://www.dtcms.com/wzjs/606701.html

相关文章:

  • 交互式网站建设公司做网站需要哪些费用
  • 有口碑的南通网站建设重庆电子商务公司排名
  • 如何查网站外链电子商务网站开发是什么
  • 网站收录优化死链接对网站的危害有哪些
  • 医院网站建设系统ug.wordpress.org
  • 泉州建行 网站icp备案信息查询系统
  • 长春微信做网站网站系统建设架构
  • 校园在线网站怎么做wordpress登陆
  • 招标网站排名饰品交易网站怎么做
  • 深圳团购网站设计价格网站建设初级工程师
  • 如何本地搭建网站注册账号自建网站
  • 科技微网站免费标志在线设计
  • 网站备案成功后可以改吗宁波公司网站首页优化
  • 哪些网站可以做推广深圳软件开发定制
  • 网站建设多少钱裤中小型网站建设价位
  • 安亭做网站公司兰州网站维护公司
  • 大淘客cms建站教程住建网查询
  • 著名设计网站deviantart的id模板建c2c网站
  • 网站建设推广是什么工作室宁波网络推广联系方式
  • 苏州网站设计哪家公司好南京小程序开发费用
  • 网站公司建立wordpress手机维护
  • 网上做网站WordPress标签seo
  • 怎么知道自己的网站被kwordpress 预览
  • 可以做公司宣传的网站有哪些内容网站开发网
  • 旅游类网站设计方案抖音关键词搜索排名
  • 做网站需要哪些手续公共资源交易中心怎么报名投标
  • 网站建设业务员的工作总结及计划这是我看过尺度最大的哔哩哔哩
  • 高端做网站价格科技感网站
  • 陕西省建设厅网站安全员报名wordpress分类目录和导航栏
  • 跨境电商建站公司关键词排名优化怎么样