如何使用 .AddJwtBearer() 在 .NET Core Web API 中验证 AWS Cognito JWT

Posted

技术标签:

【中文标题】如何使用 .AddJwtBearer() 在 .NET Core Web API 中验证 AWS Cognito JWT【英文标题】:How to validate AWS Cognito JWT in .NET Core Web API using .AddJwtBearer() 【发布时间】:2019-04-14 03:04:42 【问题描述】:

我在弄清楚如何在我的 .NET Core Web API 中验证 AWS Cognito 提供给客户端的 JWT 时遇到了一些麻烦。

我不仅不知道Microsoft.IdentityModel.Tokens.TokenValidationParameters 的变量应该是什么,而且一旦我终于知道了,我不知道如何从https://cognito-idp.region.amazonaws.com/pool ID/.well-known/jwks.json 检索JWT 密钥集

最后,尽管经过大量随机谷歌搜索和反复试验,我找到了一个(看似不是非常有效的解决方案)解决方案。然而,我花了太多时间去做这件事。引用这一点,再加上 AWS 文档严重缺乏这一事实,我决定发布此问答,以帮助其他人在未来更轻松地找到此解决方案。

如果有更好的方法可以做到这一点,请告诉我,因为除了下面列出的答案之外,我还没有找到方法。

【问题讨论】:

【参考方案1】:

这很容易成为我去年不得不使用的最困难的代码——“在 .NET Web API 应用程序中从 AWS Cognito 验证 JWT 令牌”。 AWS 文档仍有很多不足之处。

这是我用于新的 .NET 6 Web API 解决方案的内容(因此 Startup.cs 现在包含在 Program.cs 等中。如果需要,请调整以适合您的 .NET 版本):

builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddJwtBearer(options =>
    
        options.TokenValidationParameters = new TokenValidationParameters
        
            ValidIssuer = "https://cognito-idp.aws region here.amazonaws.com/Cognito UserPoolId",
            ValidateIssuerSigningKey = true,
            ValidateIssuer = true,
            ValidateLifetime = true,
            ValidAudience = "Cognito AppClientId here",
            ValidateAudience = false
        ;

        options.MetadataAddress = "https://cognito-idp.aws region here.amazonaws.com/Cognito UserPoolId here/.well-known/openid-configuration";
    );

请注意,我将 ValidateAudience 设置为 false。否则,我会从 .NET 应用程序收到 401 Unauthorized 响应。 SO上的其他人说他们必须这样做才能使OAuth的身份验证/身份验证代码授权类型起作用。显然ValidateAudience = true 可以很好地用于隐式授权。你为什么要在 2022 年使用隐式授权?

另请注意,我正在设置options.MetadataAddress。对于另一个 SO 用户来说,这显然允许在幕后缓存来自 AWS 的签名密钥,它们会不时轮换。

一些官方 AWS 文档 (boo) 让我误入歧途,这些文档让我使用 builder.Services.AddCognitoIdentity(); (.NET 6) AKA services.AddCognitoIdentity(); (.NET 5 及更早版本)。显然,这适用于后端为前端提供服务的“ASP.NET”应用程序(例如 Razor/Blazor)。或者它可能已被弃用,谁知道呢。它在 AWS 的网站上,因此很可能会被弃用...

对于控制器,类级别的简单[Authorize] 属性就足够了。无需在[Authorize] 属性中将“Bearer”指定为AuthenticationScheme,也无需创建中间件或做任何额外的事情。

如果您不想为每个控制器以及 [Authorize] 添加另一个 using (using Microsoft.AspNetCore.Authorization;),您只希望每个控制器中的每个端点都需要 JWT,您可以将其放入 Startup/程序.cs:

builder.Services.AddControllers(opt =>

    var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
    opt.Filters.Add(new AuthorizeFilter(policy));
);

确保在 Startup.cs (.NET 5) / Program.cs (.NET 6) 中 app.UseAuthenticationapp.UseAuthorization() 之前。

这里是 Startup.cs 或 Program.cs 中的usings:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.IdentityModel.Tokens;

【讨论】:

【参考方案2】:

仅当您需要对验证进行更细粒度的控制时,才需要此处提供的答案。

否则以下代码足以验证 jwt。

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>

    options.Authority = "yourAuthorizationServerAddress";
    options.Audience = "yourAudience";
);

Okta 有一篇很好的文章。 https://developer.okta.com/blog/2018/03/23/token-authentication-aspnetcore-complete-guide

JwtBearer 中间件第一次处理请求时, 尝试从授权服务器检索一些元数据(也 称为权威机构或发行人)。此元数据或发现文档 在 OpenID Connect 术语中,包含公钥和其他 验证令牌所需的详细信息。 (好奇元数据是什么样的?这是一个示例发现文档。)

如果 JwtBearer 中间件找到这个元数据文档,它 自动配置自己。很漂亮!

【讨论】:

感谢您的回答!当我在 2018 年这样做时,我无法使用您描述的方法特别是使用 AWS Cognito 和 Amplify。但是,我曾多次成功地将 JWT 与我自己的身份验证和用户管理实施一起使用。但是,如果现在可以使用,那是个好消息! 这会处理键集的旋转吗?【参考方案3】:

答案主要在于正确定义TokenValidationParameters.IssuerSigningKeyResolver(参数等见此处:https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.tokens.issuersigningkeyresolver?view=azure-dotnet)。

这就是告诉 .NET Core 验证所发送的 JWT 的内容。还必须告诉它在哪里可以找到键列表。不一定要对密钥集进行硬编码,因为它经常由 AWS 轮换。

一种方法是从IssuerSigningKeyResolver 方法中的URL 获取并序列化列表。整个.AddJwtBearer() 可能看起来像这样:

Startup.cs ConfigureServices() 方法:

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
                .AddJwtBearer(options =>
                
                    options.TokenValidationParameters = new TokenValidationParameters
                    
                        IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
                        
                            // get JsonWebKeySet from AWS
                            var json = new WebClient().DownloadString(parameters.ValidIssuer + "/.well-known/jwks.json");
                            // serialize the result
                            var keys = JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
                            // cast the result to be the type expected by IssuerSigningKeyResolver
                            return (IEnumerable<SecurityKey>)keys;
                        ,

                        ValidIssuer = "https://cognito-idp.region.amazonaws.com/pool ID",
                        ValidateIssuerSigningKey = true,
                        ValidateIssuer = true,
                        ValidateLifetime = true,
                        ValidAudience = "Cognito AppClientID",
                        ValidateAudience = true
                    ;
                );

如果您使用AWS Amplify等JS库,您可以通过观察Auth.currentSession()的结果在浏览器控制台中看到ValidIssuerValidAudience等参数

利用上面实现的 JWT 身份验证以及在控制器上使用 [Authorize] 标记,从 JS 客户端到 .NET Core Web API 的 REST 获取请求可能如下所示:

JS 客户端使用@aws-amplify/auth 节点包:

// get the current logged in user's info
Auth.currentSession().then((user) => 
fetch('https://localhost:5001/api/values',
  
    method: 'GET',
    headers: 
      // get the user's JWT token given to it by AWS cognito 
      'Authorization': `Bearer $user.signInUserSession.accessToken.jwtToken`,
      'Content-Type': 'application/json'
    
  
).then(response => response.json())
 .then(data => console.log(data))
 .catch(e => console.error(e))
)

【讨论】:

很棒的答案。客户端的放大已经改变了一点,所以对于令牌现在我们应该使用 user.signInUserSession.accessToken.jwtToken :) 与 2021 年(核心 3.1)一样,JsonConvert.DeserializeObject&lt;JsonWebKeySet&gt;(json) 可能无法使用 - 请改用:new JsonWebKeySet(json) 此示例创建一个新的 WebClient 并为每个经过身份验证的请求再次下载密钥。您可以改为在 JwtBearerOptions 上设置元数据地址,它将为您处理获取密钥和缓存。例如,`options.MetadataAddress = "cognito-idp.<region>.amazonaws.com/<pool>/…"

以上是关于如何使用 .AddJwtBearer() 在 .NET Core Web API 中验证 AWS Cognito JWT的主要内容,如果未能解决你的问题,请参考以下文章

如何在 .NET Core 3.0 中替换 AddJwtBearer 扩展

如何在 Asp.Net Core 2.0“AddJwtBearer”中间件中设置多个受众?

ASP.NET Core - AddJwtBearer - 授权 URL,它是如何工作的?

.NET Core 身份验证中的 AddJwtBearer 和 AddOpenIdConnect 有啥区别?

在 jwt 令牌验证失败后继续授权

不要在测试中验证 JWT 过期