如何在登出后立即作废Identity Core生成的访问令牌,无需等待到期
.NET5 Identity Core 登出后立即作废AccessToken实现方案
方案1:JWT黑名单机制(可实现立即作废,推荐)
核心逻辑是给每个AccessToken生成唯一标识,登出时将该标识加入黑名单,每次验证token时先校验是否在黑名单内。
实现步骤:
- 生成AccessToken时添加JWT唯一标识声明(jti)
在你的AccessTokenGenerator生成token的逻辑中,新增jti声明:
new Claim(JwtRegisteredClaimNames.Jti, Guid.NewGuid().ToString())
- 注册分布式缓存(单机可用内存缓存替代)
// 分布式部署用Redis,单机可使用services.AddMemoryCache(); services.AddStackExchangeRedisCache(options => { options.Configuration = _configuration.GetConnectionString("RedisConnection"); });
- 扩展JWT验证逻辑,增加黑名单校验
修改你的AddJwtBearer配置,添加OnTokenValidated事件:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(o => { o.TokenValidationParameters = new TokenValidationParameters() { IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(authenticationConfiguration.AccessTokenSecret)), ValidIssuer = authenticationConfiguration.Issuer, ValidAudience = authenticationConfiguration.Audience, ValidateIssuerSigningKey = true, ValidateIssuer = true, ValidateAudience = true, ClockSkew = TimeSpan.Zero }; // 新增黑名单校验逻辑 o.Events = new JwtBearerEvents { OnTokenValidated = async context => { // 获取当前token的唯一标识 var jti = context.Principal.FindFirstValue(JwtRegisteredClaimNames.Jti); var cache = context.HttpContext.RequestServices.GetRequiredService<IDistributedCache>(); // 校验是否在黑名单中 var isBlacklisted = await cache.GetStringAsync($"jwt_blacklist:{jti}"); if (!string.IsNullOrEmpty(isBlacklisted)) { context.Fail("当前凭证已作废"); } } }; });
- 登出接口新增黑名单写入逻辑
登出时除了删除刷新令牌,将当前请求的AccessToken对应的jti写入缓存,缓存过期时间设置为AccessToken的剩余有效期,避免缓存冗余:
// 示例登出接口逻辑 [HttpPost("logout")] [Authorize] public async Task<IActionResult> Logout() { var jti = User.FindFirstValue(JwtRegisteredClaimNames.Jti); var accessTokenExpireTime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(User.FindFirstValue(JwtRegisteredClaimNames.Exp))); // 写入黑名单,过期时间和token有效期一致 await _distributedCache.SetStringAsync($"jwt_blacklist:{jti}", "1", new DistributedCacheEntryOptions { AbsoluteExpiration = accessTokenExpireTime }); // 原有删除刷新令牌逻辑... return Ok(); }
方案2:缩短AccessToken有效期(改造成本最低)
如果对即时作废的要求没有极高,可将AccessToken有效期设置为3-5分钟,配合刷新令牌实现用户无感续期。此时即使登出后未作废AccessToken,最多也只能使用3-5分钟,风险窗口极小,几乎不需要修改现有逻辑。
方案3:SecurityStamp校验(适配多场景)
Identity Core自带SecurityStamp字段,可用于校验用户身份状态是否变更(登出、改密码、权限变更等):
- 生成AccessToken时添加SecurityStamp声明
new Claim("SecurityStamp", user.SecurityStamp.ToString())
- JWT验证时比对SecurityStamp
OnTokenValidated = async context => { var userId = context.Principal.FindFirstValue(ClaimTypes.NameIdentifier); var userManager = context.HttpContext.RequestServices.GetRequiredService<UserManager<User>>(); var user = await userManager.FindByIdAsync(userId); var stampInToken = context.Principal.FindFirstValue("SecurityStamp"); if (user == null || stampInToken != user.SecurityStamp) { context.Fail("当前凭证已作废"); } }
- 登出时更新用户的SecurityStamp
await _userManager.UpdateSecurityStampAsync(user);
该方案的优势是可以一次性覆盖登出、改密码、禁用用户等所有需要作废旧token的场景,缺点是每次验证需要查询数据库,性能略低。
内容的提问来源于stack exchange,提问作者Federico Alberti
相关产品推荐
相关产品推荐

