using Microsoft.AspNetCore.Http; using Microsoft.Extensions.Configuration; using QYZH.InteractiveMagazine.Infrastructure.Cache; using QYZH.InteractiveMagazine.Models.Settings; using System.IdentityModel.Tokens.Jwt; namespace QYZH.InteractiveMagazine.Infrastructure.Middleware; public class JwtAutoRefreshMiddleware { private readonly RequestDelegate _next; private readonly IConfiguration _configuration; private const string TokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token"; private const int RefreshThresholdMinutes = 10; public JwtAutoRefreshMiddleware(RequestDelegate next, IConfiguration configuration) { _next = next; _configuration = configuration; } public async Task InvokeAsync(HttpContext context) { var authHeader = context.Request.Headers.Authorization.FirstOrDefault(); if (!string.IsNullOrEmpty(authHeader) && authHeader.StartsWith("Bearer ")) { var token = authHeader.Substring("Bearer ".Length).Trim(); await TryAutoRefreshTokenAsync(context, token); } await _next(context); } private async Task TryAutoRefreshTokenAsync(HttpContext context, string token) { try { var tokenHandler = new JwtSecurityTokenHandler(); if (tokenHandler.ReadToken(token) is not JwtSecurityToken jwtToken) { return; } var expiryTime = jwtToken.ValidTo; var remainingTime = expiryTime - DateTime.Now; if (remainingTime <= TimeSpan.FromMinutes(RefreshThresholdMinutes) && remainingTime > TimeSpan.Zero) { var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value; var userName = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.Name)?.Value; if (string.IsNullOrEmpty(userId) || string.IsNullOrEmpty(userName)) { return; } var jwtSettings = _configuration.GetSection("JwtSettings").Get(); if (jwtSettings == null) { return; } var newToken = QYZH.InteractiveMagazine.Infrastructure.Auth.JwtHelper.GenerateToken( long.Parse(userId), userName, jwtSettings); await RedisHelper.StringSetAsync( $"{TokenKeyPrefix}:{userId}", newToken, TimeSpan.FromMinutes(jwtSettings.ExpiryMinutes)); context.Response.Headers["X-New-Token"] = newToken; } } catch { // 忽略自动刷新异常,由后续认证中间件处理 } } }