using Microsoft.AspNetCore.Http; using Microsoft.IdentityModel.Tokens; using System.IdentityModel.Tokens.Jwt; using System.Linq; using System.Threading.Tasks; using YLErp.Helpers; using YLErp; using System; namespace YLErp.Web.Middleware { /// /// Token续期中间件 /// 用于在用户有操作时自动续期登录状态 /// public class RefreshTokenMiddleware { private readonly RequestDelegate _next; // 续期阈值设为Token过期时间的20%,避免过于频繁的续期 private static int RefreshThreshold => (int)(AuthHelper.ExpireInSeconds * 0.2); /// /// 构造函数 /// /// 下一个中间件 public RefreshTokenMiddleware(RequestDelegate next) { _next = next; } /// /// 处理请求 /// /// HTTP上下文 public async Task InvokeAsync(HttpContext context) { // 检查是否有Access-Token if (context.Request.Cookies.TryGetValue("Access-Token", out var token)) { try { // 配置token验证参数 var validationParameters = new TokenValidationParameters { ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, IssuerSigningKey = new SymmetricSecurityKey(Convert.FromBase64String(AuthHelper.JwtSecretKey)), ValidIssuer = AuthHelper.JwtIssuer, ValidAudience = AuthHelper.JwtAudience, ClockSkew = TimeSpan.Zero // 不容忍时钟偏差 }; var tokenHandler = new JwtSecurityTokenHandler(); // 验证并解析token var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); var jwtToken = validatedToken as JwtSecurityToken; // 检查token是否接近过期 var expiration = jwtToken.ValidTo; var timeUntilExpiry = expiration - DateTime.UtcNow; // 如果剩余时间少于阈值,自动续期 if (timeUntilExpiry.TotalSeconds < RefreshThreshold) { // 从token中获取用户信息 var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == "userId")?.Value; var userName = jwtToken.Claims.FirstOrDefault(c => c.Type == "account")?.Value; var userToken = jwtToken.Claims.FirstOrDefault(c => c.Type == "uuid")?.Value; if (!string.IsNullOrEmpty(userId) && !string.IsNullOrEmpty(userName)) { // 创建新的identity var identity = UserManager.CreateIdentity(userId, userName, userToken); // 生成新的token var newToken = AuthHelper.CreateJwtToken2(identity); // 更新cookie context.Response.Cookies.Append("Access-Token", newToken, new CookieOptions { MaxAge = TimeSpan.FromSeconds(AuthHelper.ExpireInSeconds), HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict }); context.Response.Cookies.Append("Access-Token-Encrypt", YLErp.Helpers.DataProtectHelper.Encrypt(newToken), new CookieOptions { HttpOnly = true, Secure = true, SameSite = SameSiteMode.Strict }); // 记录续期日志 Console.WriteLine($"Token refreshed for user: {userName}, new expiry: {DateTime.UtcNow.AddSeconds(AuthHelper.ExpireInSeconds)}"); } } } catch (SecurityTokenExpiredException) { // Token已过期,清除cookie context.Response.Cookies.Delete("Access-Token"); context.Response.Cookies.Delete("Access-Token-Encrypt"); Console.WriteLine("Token expired, cookies cleared"); } catch (SecurityTokenInvalidSignatureException) { // Token签名无效,可能是被篡改 context.Response.Cookies.Delete("Access-Token"); context.Response.Cookies.Delete("Access-Token-Encrypt"); Console.WriteLine("Invalid token signature, cookies cleared"); } catch (Exception ex) { // 记录其他异常 Console.WriteLine($"Token refresh error: {ex.Message}"); // 继续处理请求,不影响正常流程 } } // 处理后续请求 await _next(context); } } }