2026-06-01 17:59:23 +08:00
|
|
|
using Microsoft.AspNetCore.Http;
|
|
|
|
|
using Microsoft.Extensions.Configuration;
|
2026-06-29 17:49:28 +08:00
|
|
|
using QYZH.InteractiveMagazine.Infrastructure.Auth;
|
2026-06-01 17:59:23 +08:00
|
|
|
using QYZH.InteractiveMagazine.Models.Settings;
|
|
|
|
|
using System.IdentityModel.Tokens.Jwt;
|
2026-06-30 09:15:52 +08:00
|
|
|
using System.Security.Claims;
|
2026-06-01 17:59:23 +08:00
|
|
|
|
|
|
|
|
namespace QYZH.InteractiveMagazine.Infrastructure.Middleware;
|
|
|
|
|
|
2026-06-09 15:17:16 +08:00
|
|
|
/// <summary>
|
2026-06-30 09:15:52 +08:00
|
|
|
/// Refreshes the Redis session TTL for the current JWT when it is still the active token.
|
2026-06-09 15:17:16 +08:00
|
|
|
/// </summary>
|
2026-06-01 17:59:23 +08:00
|
|
|
public class JwtAutoRefreshMiddleware
|
|
|
|
|
{
|
|
|
|
|
private readonly RequestDelegate _next;
|
|
|
|
|
private readonly IConfiguration _configuration;
|
|
|
|
|
|
|
|
|
|
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();
|
2026-06-30 09:15:52 +08:00
|
|
|
await TryRefreshRedisTokenExpiryAsync(token);
|
2026-06-01 17:59:23 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
await _next(context);
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 09:15:52 +08:00
|
|
|
private async Task TryRefreshRedisTokenExpiryAsync(string token)
|
2026-06-01 17:59:23 +08:00
|
|
|
{
|
|
|
|
|
try
|
|
|
|
|
{
|
|
|
|
|
var tokenHandler = new JwtSecurityTokenHandler();
|
|
|
|
|
if (tokenHandler.ReadToken(token) is not JwtSecurityToken jwtToken)
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 09:15:52 +08:00
|
|
|
if (jwtToken.ValidTo <= DateTime.UtcNow)
|
2026-06-01 17:59:23 +08:00
|
|
|
{
|
2026-06-09 15:17:16 +08:00
|
|
|
return;
|
|
|
|
|
}
|
2026-06-01 17:59:23 +08:00
|
|
|
|
2026-06-30 09:15:52 +08:00
|
|
|
var userId = GetClaimValue(jwtToken, ClaimTypes.NameIdentifier, JwtRegisteredClaimNames.NameId);
|
2026-06-09 15:17:16 +08:00
|
|
|
if (string.IsNullOrEmpty(userId))
|
|
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
2026-06-30 09:15:52 +08:00
|
|
|
|
2026-06-09 15:17:16 +08:00
|
|
|
var jwtSettings = _configuration.GetSection("JwtSettings").Get<JwtSettings>();
|
|
|
|
|
if (jwtSettings == null || jwtSettings.ExpiryMinutes <= 0)
|
|
|
|
|
{
|
|
|
|
|
return;
|
2026-06-01 17:59:23 +08:00
|
|
|
}
|
2026-06-09 15:17:16 +08:00
|
|
|
|
2026-06-30 09:15:52 +08:00
|
|
|
var wxUserId = GetClaimValue(jwtToken, JwtHelper.WxUserIdClaimType);
|
|
|
|
|
await RefreshRedisTokenExpiryAsync(wxUserId, userId, token, jwtSettings.ExpiryMinutes);
|
2026-06-01 17:59:23 +08:00
|
|
|
}
|
|
|
|
|
catch
|
|
|
|
|
{
|
2026-06-30 09:15:52 +08:00
|
|
|
// Ignore refresh failures. Authentication middleware will validate the request later.
|
2026-06-01 17:59:23 +08:00
|
|
|
}
|
|
|
|
|
}
|
2026-06-09 15:17:16 +08:00
|
|
|
|
2026-06-30 09:15:52 +08:00
|
|
|
private static async Task RefreshRedisTokenExpiryAsync(string? wxUserId, string userId, string currentToken, int expiryMinutes)
|
2026-06-09 15:17:16 +08:00
|
|
|
{
|
2026-06-30 09:15:52 +08:00
|
|
|
var adminTokenKey = JwtHelper.BuildAdminTokenKey(userId);
|
2026-06-09 15:17:16 +08:00
|
|
|
var adminToken = await RedisHelper.GetAsync(adminTokenKey);
|
2026-06-30 09:15:52 +08:00
|
|
|
if (string.IsNullOrEmpty(wxUserId) && !string.IsNullOrEmpty(adminToken))
|
|
|
|
|
{
|
|
|
|
|
await RedisHelper.SetAsync(adminTokenKey, adminToken, TimeSpan.FromMinutes(expiryMinutes));
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (string.IsNullOrEmpty(wxUserId))
|
2026-06-09 15:17:16 +08:00
|
|
|
{
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
2026-06-30 09:15:52 +08:00
|
|
|
var wechatTokenKey = JwtHelper.BuildWeChatTokenKey(wxUserId, userId);
|
2026-06-09 15:17:16 +08:00
|
|
|
var wechatToken = await RedisHelper.GetAsync(wechatTokenKey);
|
2026-06-30 09:15:52 +08:00
|
|
|
if (wechatToken == currentToken)
|
2026-06-09 15:17:16 +08:00
|
|
|
{
|
|
|
|
|
await RedisHelper.SetAsync(wechatTokenKey, currentToken, TimeSpan.FromMinutes(expiryMinutes));
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-06-30 09:15:52 +08:00
|
|
|
|
|
|
|
|
private static string? GetClaimValue(JwtSecurityToken jwtToken, params string[] claimTypes)
|
|
|
|
|
{
|
|
|
|
|
return jwtToken.Claims.FirstOrDefault(c => claimTypes.Contains(c.Type))?.Value;
|
|
|
|
|
}
|
2026-06-01 17:59:23 +08:00
|
|
|
}
|