IdentityServer4登录后如何获取Access Token实现缓存与登出失效
IdentityServer4 基于MemoryCache的Access Token存储、校验与即时失效实现方案
登录环节获取Access Token并存入缓存
- 不要在业务Controller的登录回调中尝试抓取Access Token,这种方式容易因为Cookie序列化字段丢失、管道流程截断拿不到原始token值,最可靠的获取点是OpenIdConnect认证管道的
OnTokenValidated事件,这个事件在应用和IdentityServer完成token交换的瞬间触发,能直接拿到完整的原始响应数据。 - 首先注册基础依赖:
builder.Services.AddMemoryCache(); - 配置OpenIdConnect认证参数时挂载事件,实现token抓取与缓存存储:
options.Events.OnTokenValidated = async context => { // 直接从token端点响应中取Access Token,无额外请求开销 var accessToken = context.TokenEndpointResponse.AccessToken; var userId = context.Principal.FindFirstValue(JwtClaimTypes.Subject); // 缓存过期时间和Access Token原生生命周期完全对齐,避免时间差 var tokenLifetime = int.Parse(context.TokenEndpointResponse.ExpiresIn.ToString()); context.HttpContext.RequestServices.GetService<IMemoryCache>().Set( key: $"user_active_token_{userId}", value: accessToken, absoluteExpirationRelativeToNow: TimeSpan.FromSeconds(tokenLifetime) ); }; - 关键注意点:缓存key固定绑定用户唯一标识,不要直接用Access Token本身做key——后者会导致登出时无法快速定位需要删除的缓存条目。
后续请求的Token合法性校验逻辑
- 校验逻辑不要写独立的请求中间件重复解析JWT,直接嵌到JwtBearer认证管道的
OnTokenValidated事件中,和原生认证流程无缝衔接:builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { // 保留原有IdentityServer基础配置:Authority、Audience等参数无需修改 options.Authority = "你的IdentityServer服务地址"; options.Audience = "受保护的API资源标识"; // 追加自定义缓存校验 options.Events.OnTokenValidated = async context => { // 直接从请求头中提取携带的Access Token var requestToken = context.Request.Headers["Authorization"].ToString().Replace("Bearer ", ""); var userId = context.Principal.FindFirstValue(JwtClaimTypes.Subject); var cache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>(); var savedValidToken = cache.Get<string>($"user_active_token_{userId}"); // 校验不通过的两种场景:缓存中已无该用户的有效token(已被登出删除)、请求携带的token和缓存中存储的有效token不匹配(使用旧token) if (string.IsNullOrEmpty(savedValidToken) || savedValidToken != requestToken) { context.Fail("当前Token已失效,请重新登录"); } }; });
登出环节的即时Token失效实现
- 解决登出后token无法移除的核心逻辑:在登出流程的最起始位置,直接删除对应绑定用户ID的缓存条目,不需要等待IdentityServer的远端登出回调完成:
[HttpPost] public async Task<IActionResult> Logout() { var cache = HttpContext.RequestServices.GetRequiredService<IMemoryCache>(); var currentUserId = User.FindFirstValue(JwtClaimTypes.Subject); // 直接移除缓存中存储的对应用户有效token cache.Remove($"user_active_token_{currentUserId}"); // 后续接入原有登出流程:清除本地认证Cookie、跳转IdentityServer的EndSession端点等 await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); return SignOut( new AuthenticationProperties { RedirectUri = "/" }, OpenIdConnectDefaults.AuthenticationScheme ); } - 常见踩坑提醒:
- 不要把缓存删除逻辑放在远端登出的回调事件中,否则从用户点击登出到回调完成的窗口期内,旧token依然可以正常访问接口,达不到即时失效的效果
- 如果应用是多实例部署,单实例的MemoryCache存在数据不互通的问题,可以直接将
IMemoryCache替换为IDistributedCache的分布式实现(如Redis),现有业务逻辑无需改动 - 不要依赖IdentityServer默认的JWT无状态校验做全量判断,默认的JWT校验只会验证签名、有效期,无法感知用户主动登出的状态,必须搭配缓存校验才能实现即时失效
内容的提问来源于stack exchange,提问作者Hoang Tran
相关产品推荐
相关产品推荐

