Files
zszq-trs/YLErpWeb/Middleware/RefreshTokenMiddleware.cs
T
hjhan 276d53f986 feat(security): 添加JWT令牌受众验证和详细日志记录
- 在AuthHelper中为JWT令牌添加Issuer和Audience字段
- 在RefreshTokenMiddleware中添加SecurityTokenValidationException异常处理
- 添加Token audience验证失败的日志记录和错误处理
- 为SwapEodPositionService中的DealInterests方法添加参数验证日志
- 为SaveAutoEodInterestPosition方法添加详细的参数验证和空值检查
- 增强错误日志输出,包括令牌前缀和异常类型信息
2026-03-23 17:32:16 +08:00

140 lines
6.7 KiB
C#

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
{
/// <summary>
/// Token续期中间件
/// 用于在用户有操作时自动续期登录状态
/// </summary>
public class RefreshTokenMiddleware
{
private readonly RequestDelegate _next;
// 续期阈值设为Token过期时间的20%,避免过于频繁的续期
private static int RefreshThreshold => (int)(AuthHelper.ExpireInSeconds * 0.2);
/// <summary>
/// 构造函数
/// </summary>
/// <param name="next">下一个中间件</param>
public RefreshTokenMiddleware(RequestDelegate next)
{
_next = next;
}
/// <summary>
/// 处理请求
/// </summary>
/// <param name="context">HTTP上下文</param>
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 (SecurityTokenValidationException ex) when (ex.Message.Contains("IDX10206"))
{
// Token audience 验证失败,可能是旧token没有audience
LogFactory.GetLogger("RefreshTokenMiddleware").Error($"[Token刷新] Audience验证失败 - Token: {token?.Substring(0, Math.Min(20, token?.Length ?? 0))}..., ValidIssuer: {AuthHelper.JwtIssuer}, ValidAudience: {AuthHelper.JwtAudience}", ex);
context.Response.Cookies.Delete("Access-Token");
context.Response.Cookies.Delete("Access-Token-Encrypt");
Console.WriteLine("Token audience validation failed, cookies cleared");
}
catch (Exception ex)
{
// 记录其他异常
var logger = LogFactory.GetLogger("RefreshTokenMiddleware");
logger.Error($"[Token刷新] 未知错误 - Token: {token?.Substring(0, Math.Min(20, token?.Length ?? 0))}..., 异常类型: {ex.GetType().Name}", ex);
Console.WriteLine($"Token refresh error: {ex.Message}");
// 继续处理请求,不影响正常流程
}
}
// 处理后续请求
await _next(context);
}
}
}