1. 调整JWT令牌过期时间配置,新增JwtTokenExpiryDays配置项 2. 分离Token存储前缀,区分管理后台和微信端Token 3. 新增Redis Token有效性校验逻辑,拦截无效/已注销的Token 4. 简化自动刷新逻辑,改为每次请求刷新Redis Token过期时间 5. 完善相关注释和代码结构
102 lines
3.5 KiB
C#
102 lines
3.5 KiB
C#
using Microsoft.AspNetCore.Http;
|
||
using Microsoft.Extensions.Configuration;
|
||
using QYZH.InteractiveMagazine.Models.Settings;
|
||
using System.IdentityModel.Tokens.Jwt;
|
||
|
||
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
||
|
||
/// <summary>
|
||
/// JWT 自动刷新中间件
|
||
/// 功能:
|
||
/// 每次请求都刷新 Redis 中 Token 的过期时间(保持会话活跃)
|
||
/// 前端永远使用同一个 Token,无需处理 Token 刷新
|
||
/// </summary>
|
||
public class JwtAutoRefreshMiddleware
|
||
{
|
||
private readonly RequestDelegate _next;
|
||
private readonly IConfiguration _configuration;
|
||
private const string AdminTokenKeyPrefix = "InteractiveMagazine:AdminAuth:Token";
|
||
private const string WeChatTokenKeyPrefix = "InteractiveMagazine:WeChatAuth:Token";
|
||
|
||
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 TryRefreshRedisTokenExpiryAsync(context, token);
|
||
}
|
||
|
||
await _next(context);
|
||
}
|
||
|
||
private async Task TryRefreshRedisTokenExpiryAsync(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;
|
||
|
||
// JWT 已过期,不处理
|
||
if (remainingTime <= TimeSpan.Zero)
|
||
{
|
||
return;
|
||
}
|
||
|
||
var userId = jwtToken.Claims.FirstOrDefault(c => c.Type == System.Security.Claims.ClaimTypes.NameIdentifier)?.Value;
|
||
if (string.IsNullOrEmpty(userId))
|
||
{
|
||
return;
|
||
}
|
||
|
||
var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>();
|
||
if (jwtSettings == null || jwtSettings.ExpiryMinutes <= 0)
|
||
{
|
||
return;
|
||
}
|
||
|
||
// 每次请求都刷新 Redis 中 Token 的过期时间
|
||
await RefreshRedisTokenExpiryAsync(userId, token, jwtSettings.ExpiryMinutes);
|
||
}
|
||
catch
|
||
{
|
||
// 忽略自动刷新异常,由后续认证中间件处理
|
||
}
|
||
}
|
||
|
||
/// <summary>
|
||
/// 刷新 Redis 中已存在 Token 的过期时间(保持会话活跃)
|
||
/// </summary>
|
||
private async Task RefreshRedisTokenExpiryAsync(string userId, string currentToken, int expiryMinutes)
|
||
{
|
||
// 检查 Admin Token
|
||
var adminTokenKey = $"{AdminTokenKeyPrefix}:{userId}";
|
||
var adminToken = await RedisHelper.GetAsync(adminTokenKey);
|
||
if (!string.IsNullOrEmpty(adminToken))
|
||
{
|
||
await RedisHelper.SetAsync(adminTokenKey, currentToken, TimeSpan.FromMinutes(expiryMinutes));
|
||
return;
|
||
}
|
||
|
||
// 检查 WeChat Token
|
||
var wechatTokenKey = $"{WeChatTokenKeyPrefix}:{userId}";
|
||
var wechatToken = await RedisHelper.GetAsync(wechatTokenKey);
|
||
if (!string.IsNullOrEmpty(wechatToken))
|
||
{
|
||
await RedisHelper.SetAsync(wechatTokenKey, currentToken, TimeSpan.FromMinutes(expiryMinutes));
|
||
}
|
||
}
|
||
}
|