在 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 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
{
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');