You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.25 06:04:56