AWS Cognito控制台登出用户无效,ASP.NET Core 5.0 MVC仍可访问受保护资源
AWS Cognito控制台登出/禁用用户后仍可访问ASP.NET Core受保护资源的解决方案
问题根源
ASP.NET Core默认的JWT认证中间件仅做基础校验:验证令牌签名、过期时间、发行者和受众是否合法,不会主动向Cognito确认令牌是否已被撤销(比如控制台的「Sign out user」操作),也不会检查用户账号是否被禁用。要解决这个问题,必须补充令牌实时有效性校验的逻辑。
具体实现步骤
1. 启用Cognito令牌自省(Token Introspection)
Cognito提供了专门的令牌自省端点,用来验证访问令牌/ID令牌的实时状态,包括是否失效、用户是否被禁用等。
第一步:添加必要依赖
确保项目引用Microsoft.AspNetCore.Authentication.JwtBearer,如果要直接调用Cognito SDK,还需要AWSSDK.CognitoIdentityProvider。
第二步:修改JWT认证配置,添加自定义校验逻辑
在Startup.cs的ConfigureServices方法(ASP.NET Core 5.0)里,调整JWT中间件的配置,加入OnTokenValidated事件来触发自省校验:
services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme) .AddJwtBearer(options => { options.Authority = "https://cognito-idp.<你的区域>.amazonaws.com/<你的用户池ID>"; options.Audience = "<你的客户端ID>"; options.Events = new JwtBearerEvents { OnTokenValidated = async context => { var jwtToken = context.SecurityToken as JwtSecurityToken; if (jwtToken == null) { context.Fail("无效令牌"); return; } // 构造自省请求 using var httpClient = new HttpClient(); var formData = new FormUrlEncodedContent(new Dictionary<string, string> { {"token", jwtToken.RawData}, {"token_type_hint", "access_token"}, // 用ID令牌的话改成"id_token" {"client_id", "<你的客户端ID>"}, {"client_secret", "<你的客户端密钥>"} // 机密客户端必须填,公开客户端不需要 }); // 调用Cognito自省端点 var response = await httpClient.PostAsync( $"https://cognito-idp.<你的区域>.amazonaws.com/<你的用户池ID>/oauth2/introspect", formData); if (!response.IsSuccessStatusCode) { context.Fail("令牌校验失败"); return; } var introspectResult = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>(); if (!(bool)introspectResult["active"]) { context.Fail("令牌已失效或用户已被禁用"); } } }; });
2. 优化性能:添加缓存机制
每次请求都调用Cognito端点会增加延迟,建议用IMemoryCache缓存校验结果,缓存时长设为令牌的剩余有效期:
options.Events = new JwtBearerEvents { OnTokenValidated = async context => { var jwtToken = context.SecurityToken as JwtSecurityToken; if (jwtToken == null) { context.Fail("无效令牌"); return; } var cache = context.HttpContext.RequestServices.GetRequiredService<IMemoryCache>(); var cacheKey = $"TokenValid_{jwtToken.RawData}"; // 先查缓存,没有再调用自省端点 if (!cache.TryGetValue(cacheKey, out bool isTokenActive)) { using var httpClient = new HttpClient(); var formData = new FormUrlEncodedContent(new Dictionary<string, string> { {"token", jwtToken.RawData}, {"token_type_hint", "access_token"}, {"client_id", "<你的客户端ID>"}, {"client_secret", "<你的客户端密钥>"} }); var response = await httpClient.PostAsync( $"https://cognito-idp.<你的区域>.amazonaws.com/<你的用户池ID>/oauth2/introspect", formData); if (!response.IsSuccessStatusCode) { context.Fail("令牌校验失败"); return; } var introspectResult = await response.Content.ReadFromJsonAsync<Dictionary<string, object>>(); isTokenActive = (bool)introspectResult["active"]; // 缓存到令牌过期时间 var cacheOptions = new MemoryCacheEntryOptions() .SetAbsoluteExpiration(jwtToken.ValidTo); cache.Set(cacheKey, isTokenActive, cacheOptions); } if (!isTokenActive) { context.Fail("令牌已失效或用户已被禁用"); } } };
3. 完善登出逻辑(确保Cognito会话完全失效)
虽然你已经实现了清除Cookie和调用end_session,但要确认请求携带id_token_hint参数,保证Cognito彻底撤销会话:
public async Task<IActionResult> Logout() { // 获取当前用户的ID令牌 var idToken = await HttpContext.GetTokenAsync("id_token"); // 构造Cognito登出URL var logoutUrl = $"https://<你的Cognito用户池域名>/logout?client_id=<你的客户端ID>&logout_uri=<你的登出回调URL>&id_token_hint={idToken}"; // 清除本地认证Cookie await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme); await HttpContext.SignOutAsync(JwtBearerDefaults.AuthenticationScheme); return Redirect(logoutUrl); }
可选:直接调用Cognito API检查用户状态
如果不需要完整的令牌自省,也可以调用Cognito的AdminGetUser API直接检查用户是否被禁用:
options.Events = new JwtBearerEvents { OnTokenValidated = async context => { var username = context.Principal.FindFirst(ClaimTypes.NameIdentifier)?.Value; if (string.IsNullOrEmpty(username)) { context.Fail("无法获取用户名"); return; } // 初始化Cognito客户端 var cognitoClient = new AmazonCognitoIdentityProviderClient(RegionEndpoint.<你的区域>); var getUserRequest = new AdminGetUserRequest { UserPoolId = "<你的用户池ID>", Username = username }; var getUserResponse = await cognitoClient.AdminGetUserAsync(getUserRequest); if (getUserResponse.Enabled != true) { context.Fail("用户已被禁用"); } } };
注意事项
- 客户端类型:如果你的Cognito客户端是「公开客户端」(比如纯前端应用),不需要提供
client_secret;如果是「机密客户端」(比如Web应用),必须填写客户端密钥。 - IAM权限:调用自省端点需要
cognito-idp:IntrospectToken权限,调用AdminGetUser需要cognito-idp:AdminGetUser权限,确保运行应用的IAM角色拥有这些权限。 - 缓存策略:缓存时长不要超过令牌的有效期,避免缓存过期令牌的有效状态。
内容的提问来源于stack exchange,提问作者Dean P
相关产品推荐
相关产品推荐

