diff --git a/YLErpDAL/Helpers/AuthHelper.cs b/YLErpDAL/Helpers/AuthHelper.cs index eeb1e867..3f88374c 100644 --- a/YLErpDAL/Helpers/AuthHelper.cs +++ b/YLErpDAL/Helpers/AuthHelper.cs @@ -56,9 +56,16 @@ namespace YLErp.Helpers { var key = new SymmetricSecurityKey(Convert.FromBase64String(JwtSecretKey)); + // 添加 JTI (JWT ID) 用于黑名单机制 + var claims = new ClaimsIdentity(identity); + if (!claims.HasClaim(c => c.Type == "jti")) + { + claims.AddClaim(new Claim("jti", Guid.NewGuid().ToString("N"))); + } + var tokenDescriptor = new SecurityTokenDescriptor { - Subject = new ClaimsIdentity(identity), + Subject = claims, Expires = DateTime.UtcNow.AddSeconds(ExpireInSeconds), IssuedAt = DateTime.Now, Issuer = JwtIssuer, diff --git a/YLErpWeb/Controllers/AccountController.cs b/YLErpWeb/Controllers/AccountController.cs index 3adc4f8d..e9fe8a92 100644 --- a/YLErpWeb/Controllers/AccountController.cs +++ b/YLErpWeb/Controllers/AccountController.cs @@ -311,6 +311,35 @@ namespace YLErp.Web.Controllers HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme); logger.Info($"[退出登录] SignOutAsync 已调用"); + // 清除 JWT Token Cookie + Response.Cookies.Delete("Access-Token"); + Response.Cookies.Delete("Access-Token-Encrypt"); + logger.Info($"[退出登录] JWT Token Cookie 已清除"); + + // 将当前 JWT Token 加入黑名单(使其立即失效) + if (Request.Cookies.TryGetValue("Access-Token", out var accessToken) && !string.IsNullOrEmpty(accessToken)) + { + try + { + var tokenHandler = new System.IdentityModel.Tokens.Jwt.JwtSecurityTokenHandler(); + var jwtToken = tokenHandler.ReadJwtToken(accessToken); + var jti = jwtToken.Claims.FirstOrDefault(c => c.Type == "jti")?.Value ?? accessToken.Substring(0, 32); + var expiry = jwtToken.ValidTo; + var ttl = expiry - DateTime.UtcNow; + + if (ttl.TotalSeconds > 0) + { + // 将 Token 加入黑名单,过期时间与 Token 过期时间相同 + Server.CacheProvider.Set($"token_blacklist:{jti}", userId.ToString(), ttl); + logger.Info($"[退出登录] Token 已加入黑名单 - JTI: {jti}, 过期时间: {expiry:yyyy-MM-dd HH:mm:ss}"); + } + } + catch (Exception ex) + { + logger.Error($"[退出登录] Token 加入黑名单失败", ex); + } + } + if (SsoLoginConfig.SSO_Enable) { switch (loginName?.ToLowerInvariant()) diff --git a/YLErpWeb/Middleware/RefreshTokenMiddleware.cs b/YLErpWeb/Middleware/RefreshTokenMiddleware.cs index acc69f4a..2ace3483 100644 --- a/YLErpWeb/Middleware/RefreshTokenMiddleware.cs +++ b/YLErpWeb/Middleware/RefreshTokenMiddleware.cs @@ -54,6 +54,22 @@ namespace YLErp.Web.Middleware var tokenHandler = new JwtSecurityTokenHandler(); + // 先解析token获取JTI,检查是否在黑名单中 + var jwtTokenForCheck = tokenHandler.ReadJwtToken(token); + var jti = jwtTokenForCheck.Claims.FirstOrDefault(c => c.Type == "jti")?.Value ?? token.Substring(0, 32); + + // 检查token是否在黑名单中 + var blacklisted = Server.CacheProvider.Get($"token_blacklist:{jti}"); + if (blacklisted != null) + { + LogFactory.GetLogger("RefreshTokenMiddleware").Info($"[Token验证] Token已在黑名单中,拒绝访问 - JTI: {jti}"); + context.Response.Cookies.Delete("Access-Token"); + context.Response.Cookies.Delete("Access-Token-Encrypt"); + context.Response.StatusCode = 401; + await context.Response.WriteAsync("Token已失效,请重新登录"); + return; + } + // 验证并解析token var principal = tokenHandler.ValidateToken(token, validationParameters, out var validatedToken); var jwtToken = validatedToken as JwtSecurityToken;