ASP.NET Core中 JWT 实现无感刷新Token

ops/2025/1/19 12:34:12/

在 Web 应用开发中,用户登录状态的管理至关重要。为了避免用户频繁遇到登录过期的问题,我们可以通过实现 JWT(JSON Web Token)刷新机制来提升用户体验

推荐: 使用 Refresh Token(双 Token 机制)

1. 生成和使用双 Token

通常会生成两种 Token:访问 Token (Access Token) 和 刷新 Token (Refresh Token)

  • • 访问 Token:用于客户端与服务器之间的身份验证,有效期较短(例如 30 分钟),以提高安全性。

  • • 刷新 Token:用于获取新的访问 Token,有效期较长(例如 7 天),存储在客户端 。

Tokenservice.cs:

 
// 生成 JWT Access Token 和 Refresh Token
public (string AccessToken, string RefreshToken) GenerateTokens(string userId, string userName)
{var tokenHandler = new JwtSecurityTokenHandler();var key = Encoding.ASCII.GetBytes(_configuration["JwtSettings:Secret"]);// 生成 Access Tokenvar accessTokenDescriptor = new SecurityTokenDescriptor{Subject = new ClaimsIdentity(new Claim[]{new Claim(ClaimTypes.NameIdentifier, userId),new Claim(ClaimTypes.Name, userName)}),Issuer = _configuration["JwtSettings:Issuer"],Audience = _configuration["JwtSettings:Audience"],Expires = DateTime.UtcNow.AddMinutes(double.Parse(_configuration["JwtSettings:ExpireMinutes"])),SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)};var accessToken = tokenHandler.CreateToken(accessTokenDescriptor);// 生成 Refresh Tokenvar refreshTokenDescriptor = new SecurityTokenDescriptor{Subject = new ClaimsIdentity(new Claim[]{new Claim(ClaimTypes.NameIdentifier, userId)}),Issuer = _configuration["JwtSettings:Issuer"],Audience = _configuration["JwtSettings:Audience"],Expires = DateTime.UtcNow.AddDays(7), // Refresh Token 有效期 7 天SigningCredentials = new SigningCredentials(new SymmetricSecurityKey(key), SecurityAlgorithms.HmacSha256Signature)};var refreshToken = tokenHandler.CreateToken(refreshTokenDescriptor);return (tokenHandler.WriteToken(accessToken), tokenHandler.WriteToken(refreshToken));
}// 根据旧 JWT 令牌换取新 JWT 令牌public string ExchangeJwtToken(string oldToken){if (!ValidateJwtToken(oldToken)){thrownew InvalidOperationException("Invalid or expired token");}var principal = ParseJwtToken(oldToken);var userId = principal.FindFirst(ClaimTypes.NameIdentifier)?.Value;var userName = principal.FindFirst(ClaimTypes.Name)?.Value;if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(userName)){thrownew InvalidOperationException("Invalid token claims");}return GenerateJwtToken(userId, userName);}

AuthController.cs

private readonly TokenService _tokenService;
privatereadonly IAppUser _appUser;public AuthController(IConfiguration configuration, IAppUser appUser, TokenService tokenService)
{_tokenService = tokenService;_appUser = appUser;
}[HttpPost("login")]
[AllowAnonymous]
public IActionResult Login([FromBody] LoginRequest loginRequest)
{var token = _tokenService.GenerateTokens(loginRequest.Username, loginRequest.Username);return Ok(new { token.RefreshToken,token.AccessToken });
}

2. 前端请求拦截器自动刷新 Token

在前端应用中,可以使用请求拦截器来自动处理 Token 刷新逻辑。当访问 Token 过期时,拦截器会自动调用刷新接口获取新的访问 Token,并重新发起请求

//  请求拦截器
axios.interceptors.request.use(
config => {const token = localStorage.getItem('accessToken');if (token) {config.headers['Authorization'] = `Bearer ${token}`;}return config;},
error => {returnPromise.reject(error);}
);axios.interceptors.response.use(
response => response,
async error => {const originalRequest = error.config;if (error.response.status === 401 && !originalRequest._retry) {originalRequest._retry = true;const refreshToken = localStorage.getItem('refreshToken');const res = await axios.post('/api/auth/refresh-token', { refreshToken });localStorage.setItem('accessToken', res.data.accessToken);returnaxios(originalRequest);}returnPromise.reject(error);}
);

3. 后端提供刷新 Token 的接口

专门的接口来处理刷新 Token 的请求。该接口会验证刷新 Token 的有效性,并返回新的访问 Token

 // 刷新 Access Token
[HttpPost("refresh")]
public IActionResult RefreshToken([FromBody] RefreshTokenRequest request)
{try{var validatetoken = _tokenService.ValidateJwtToken(request.RefreshToken);if (validatetoken){return Ok(_tokenService.ExchangeJwtToken(request.RefreshToken));}return Unauthorized("Invalid refresh token");}catch (Exception ex){return Unauthorized("Invalid refresh token");}
}

不推荐:中间件自动刷新Token

  1. 1. 创建中间件:创建一个中间件,用于检查每个请求的JWT Token过期时间。

  2. 2. 读取Token:中间件读取请求头中的Authorization字段,获取JWT Token。

  3. 3. 检查过期时间:判断Token的过期时间,如果距离过期时间在一定范围内(如30分钟内),则生成一个新的JWT Token,并通过自定义的响应头(如X-Refresh-Token)返回给客户端。

  4. 4. 客户端更新Token:客户端检查响应头,如果存在X-Refresh-Token,则用新Token替换旧Token,并在后续请求中使用新Token。

  5. 5. 弊端:虽然实现起来相对简单,但安全性相对较低,因为Token的刷新是在客户端自动进行的,如果Token被盗用,攻击者可能会在Token过期前一直使用该Token

因为存在上述弊端,不推荐使用中间件自动刷新 Token,故不提供相关代码实现。


http://www.ppmy.cn/ops/151383.html

相关文章

【JavaEE进阶】实现简单的加法计算器和用户登录

目录 🎍序言 🌳加法计算器 🚩准备工作 🚩约定前后端交互接口 🚩后端服务器代码的书写 🌴用户登录 🚩准备工作 🚩约定前后端交互接口 🏀需求分析 🏀…

Redis - 环境搭建

目录 在Ubuntu上安装Redis在Centos上安装RedisCentos7Centos8 版本:使用Redis5系列 操作系统:在Linux中进行安装(Redis官方不支持Windows版本) 在Ubuntu上安装Redis 更新软件包: sudo apt-get update搜索相关软件包…

清除前端缓存的方式

1.定义 浏览器缓存(Browser Caching)是为了节约网络的资源加速浏览,浏览器在用户磁盘上对最近请求过的文档进行存储,当访问者再次请求这个页面时,浏览器就可以从本地磁盘显示文档,这样就可以加速页面…

Objective-C语言的数据类型

Objective-C数据类型详解 Objective-C是一种面向对象的编程语言,主要用于macOS和iOS应用程序的开发。作为C语言的超集,Objective-C继承了C语言的基本数据类型,同时也引入了一些独特的特性。本文将对Objective-C的各种数据类型进行详细的介绍…

docker hello world

引言 Docker是一个容器化平台,能够把应用程序及其依赖打包在一个容器中,让容器在任意地方运行。最近刚了解了Docker,自己动手操作了下,做个记录,也希望可以帮助到刚学习Docker的小伙伴。 本文中已使用管理员模式&…

若依入门使用

若依使用 1.下载2.搭建环境2.1 若依框架的目录结构2.2 打开项目修改配置文件2.2.1 打开后端项目2.2.2 设置 Maven2.2.3 application-druid.yml文件2.2.4 application.yml文件 2.3 运行后台服务2.4 运行前台服务2.4.1 编译器打开前台项目2.4.2 项目初始化2.4.3 启动 3.使用3.1 菜…

LeetCode 383. 赎金信

在本篇博客中,我们将探讨LeetCode上的一个经典问题:383. 赎金信。这个问题考察了我们对字符串处理和字符计数的理解和应用。 问题描述 解题思路 这个问题可以通过字符计数的方法来解决。我们首先统计 magazine 中每个字符出现的次数,然后检…

【Linux】常见指令(一)

Linux常见指令 01.whoami02.pwd03.ls04.mkdir05.cd 本文LInux环境为,使用XShell远程登陆到Linux。 具体如何环境搭建,大家可以查看其他博客。 01.whoami whoami 指令用来查看当前账户是谁。 如上图所示,使用whoami指令,查看到现在…