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

360影视 2025-01-18 09:33 2

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

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

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

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

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

Tokenservice.cs:


// 生成 JWT Access Token 和 Refresh Token
public (stringAccessToken,stringRefreshToken) GenerateTokens(stringuserId,stringuserName)
{
var tokenHandler = new JwtSecurityTokenHandler;
var key = Encoding.ASCII.GetBytes(_configuration["JwtSettings:Secret"]);

// 生成 Access Token
var 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 Token
var refreshTokenDescriptor = new SecurityTokenDescriptor
{

{
new Claim(ClaimTypes.NameIdentifier, userId)
}),

Expires = DateTime.UtcNow.AddDays(7),// Refresh Token 有效期 7 天

};
var refreshToken = tokenHandler.CreateToken(refreshTokenDescriptor);

return (tokenHandler.WriteToken(accessToken), tokenHandler.WriteToken(refreshToken));
}

// 根据旧 JWT 令牌换取新 JWT 令牌
publicstringExchangeJwtToken(stringoldToken)
{
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.IsOrEmpty(userId) ||string.IsOrEmpty(userName))
{
thrownew InvalidOperationException("Invalid token claims");
}

return GenerateJwtToken(userId, userName);
}

AuthController.cs

private readonly TokenService _tokenService;
privatereadonly IAppUser _appUser;

publicAuthController(IConfiguration configuration, IAppUser appUser, TokenService tokenService)
{
_tokenService = tokenService;
_appUser = appUser;
}

[HttpPost("login")]
[AllowAnonymous]
public IActionResultLogin([FromBody] Loginrequest loginRequest)
{
var token = _tokenService.GenerateTokens(loginRequest.Username, loginRequest.Username);
return Ok(new { token.RefreshToken,token.AccessToken });
}

在前端应用中,可以使用请求拦截器来自动处理 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);
}
);

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

// 刷新 Access Token
[HttpPost("refresh")]
public IActionResultRefreshToken([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)
{

}
}

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

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

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

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

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

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

来源:opendotnet

相关推荐