From f182b880c178ede795896501f871ece18c5a69c3 Mon Sep 17 00:00:00 2001 From: hjhan Date: Mon, 23 Mar 2026 17:52:15 +0800 Subject: [PATCH] =?UTF-8?q?feat(auth):=20=E5=AE=9E=E7=8E=B0JWT=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E9=BB=91=E5=90=8D=E5=8D=95=E6=9C=BA=E5=88=B6=20-=20?= =?UTF-8?q?=E5=9C=A8=E9=80=80=E5=87=BA=E7=99=BB=E5=BD=95=E6=97=B6=E6=B8=85?= =?UTF-8?q?=E9=99=A4JWT=20Token=20Cookie=E5=B9=B6=E5=B0=86=E5=85=B6?= =?UTF-8?q?=E5=8A=A0=E5=85=A5=E9=BB=91=E5=90=8D=E5=8D=95=20-=20=E6=B7=BB?= =?UTF-8?q?=E5=8A=A0JTI=E5=A3=B0=E6=98=8E=E5=88=B0JWT=E4=BB=A4=E7=89=8C?= =?UTF-8?q?=E7=94=A8=E4=BA=8E=E5=94=AF=E4=B8=80=E6=A0=87=E8=AF=86=E5=92=8C?= =?UTF-8?q?=E9=BB=91=E5=90=8D=E5=8D=95=E7=AE=A1=E7=90=86=20-=20=E5=9C=A8?= =?UTF-8?q?=E4=B8=AD=E9=97=B4=E4=BB=B6=E4=B8=AD=E9=AA=8C=E8=AF=81=E4=BB=A4?= =?UTF-8?q?=E7=89=8C=E6=97=B6=E6=A3=80=E6=9F=A5=E9=BB=91=E5=90=8D=E5=8D=95?= =?UTF-8?q?=E7=8A=B6=E6=80=81=E5=B9=B6=E6=8B=92=E7=BB=9D=E5=B7=B2=E6=B3=A8?= =?UTF-8?q?=E9=94=80=E7=9A=84=E4=BB=A4=E7=89=8C=20-=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=E4=BB=A4=E7=89=8C=E7=AB=8B=E5=8D=B3=E5=A4=B1=E6=95=88=E5=8A=9F?= =?UTF-8?q?=E8=83=BD=E9=98=B2=E6=AD=A2=E5=B7=B2=E9=80=80=E5=87=BA=E7=94=A8?= =?UTF-8?q?=E6=88=B7=E7=BB=A7=E7=BB=AD=E8=AE=BF=E9=97=AE=E7=B3=BB=E7=BB=9F?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- YLErpDAL/Helpers/AuthHelper.cs | 9 +++++- YLErpWeb/Controllers/AccountController.cs | 29 +++++++++++++++++++ YLErpWeb/Middleware/RefreshTokenMiddleware.cs | 16 ++++++++++ 3 files changed, 53 insertions(+), 1 deletion(-) 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;