.Net Core实现JWT令牌内存缓存校验,解决注销后即时失效需求
实现JWT令牌注销后立即失效的高效方案
核心思路
借助JWT验证后的扩展事件,在令牌签名验证通过后,额外检查内存缓存中是否存在当前用户邮箱对应的有效令牌。若缓存中无对应条目或令牌不匹配,则直接拒绝请求,无需在每个API中编写重复检查逻辑。
步骤1:确保JWT包含邮箱声明
创建JWT令牌时,必须将用户邮箱ID作为ClaimTypes.Email声明加入令牌,这样后续才能从令牌中解析出邮箱ID用于缓存校验:
// 创建JWT令牌的示例代码 var claims = new List<Claim> { new Claim(ClaimTypes.Email, ob.MailId), // 关键:添加邮箱声明 new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString()), // 其他业务声明... }; var token = new JwtSecurityToken( issuer: configuration["Jwt:Issuer"], audience: configuration["Jwt:Audience"], claims: claims, expires: DateTime.Now.AddMinutes(30), // 与缓存过期时间保持一致 signingCredentials: new SigningCredentials(new SymmetricSecurityKey(Encoding.UTF8.GetBytes(configuration["Jwt:Key"])), SecurityAlgorithms.HmacSha256) ); // 存入内存缓存,同时设置与JWT一致的过期时间 _memoryCache.Set(ob.MailId, new JwtSecurityTokenHandler().WriteToken(token), new MemoryCacheEntryOptions { AbsoluteExpirationRelativeToNow = TimeSpan.FromMinutes(30) });
步骤2:配置JWT验证事件添加缓存校验
在Program.cs的JWT认证配置中,添加OnTokenValidated事件处理逻辑,完成缓存校验:
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 原有JWT验证参数配置(保持不变) options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = true, ValidateAudience = true, ValidateLifetime = true, ValidateIssuerSigningKey = true, ValidIssuer = builder.Configuration["Jwt:Issuer"], ValidAudience = builder.Configuration["Jwt:Audience"], IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["Jwt:Key"])) }; // 添加令牌验证后的缓存校验逻辑 options.Events = new JwtBearerEvents { OnTokenValidated = context => { var memoryCache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>(); // 从令牌解析邮箱声明 var emailClaim = context.Principal?.FindFirst(ClaimTypes.Email); if (emailClaim == null) { context.Fail("缺少邮箱身份声明"); return Task.CompletedTask; } // 获取当前请求的令牌 var requestToken = context.Request.Headers.Authorization.FirstOrDefault()?.Split(" ").Last(); if (string.IsNullOrEmpty(requestToken)) { context.Fail("令牌为空"); return Task.CompletedTask; } // 校验缓存中是否存在匹配的有效令牌 if (!memoryCache.TryGetValue(emailClaim.Value, out string cachedToken) || !string.Equals(cachedToken, requestToken, StringComparison.Ordinal)) { context.Fail("令牌已失效"); } return Task.CompletedTask; } }; });
步骤3:注销逻辑实现
用户注销时,只需从内存缓存中移除对应邮箱的令牌条目即可:
[ApiController] [Route("api/auth")] public class AuthController : ControllerBase { private readonly IMemoryCache _memoryCache; public AuthController(IMemoryCache memoryCache) { _memoryCache = memoryCache; } [HttpPost("logout")] [Authorize] public IActionResult Logout() { var email = User.FindFirst(ClaimTypes.Email)?.Value; if (!string.IsNullOrEmpty(email)) { _memoryCache.Remove(email); } return Ok("注销成功"); } }
注意事项
- 缓存过期对齐:内存缓存的过期时间必须与JWT令牌的过期时间保持一致,避免缓存中残留过期令牌导致无效请求。
- 多服务器场景:如果是多服务器部署,内存缓存无法跨进程共享,需替换为分布式缓存(如Redis),确保所有节点的缓存状态一致。
- 响应规范:
context.Fail()会自动触发ASP.NET Core返回401 Unauthorized响应,符合REST API的标准错误处理,无需手动抛出UnauthorizedAccessException(若强制抛出,可在context.Fail()后抛出,但不推荐破坏默认响应流程)。
全局授权(可选)
如果所有API都需要应用该校验,可全局配置授权策略,无需在每个API上添加[Authorize]特性:
builder.Services.AddControllers(options => { var authPolicy = new AuthorizationPolicyBuilder() .AddAuthenticationSchemes(JwtBearerDefaults.AuthenticationScheme) .RequireAuthenticatedUser() .Build(); options.Filters.Add(new AuthorizeFilter(authPolicy)); });
内容的提问来源于stack exchange,提问作者Shounak
相关产品推荐
相关产品推荐

