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

How to validate AWS Cognito JWT in .NET Core Web API using .AddJwtBearer()

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

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

最后,尽管进行了大量的随机谷歌搜索和反复试验,我找到了一个(看似不是很有效的解决方案)解决方案。但是,我花了太多时间去做这件事。引用这一点,再加上严重缺乏有关该主题的 AWS 文档,我决定 post 此问答以帮助其他人将来更轻松地找到此解决方案。

如果有更好的方法,请告诉我,因为除了下面列出的答案,我还没有找到方法。

答案主要在于正确定义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 等参数20=]

从 JS 客户端到 .NET Core Web 的 REST 获取请求 API 利用上面实现的 JWT 身份验证以及在控制器上使用 [Authorize] 标记可能看起来像这样:

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))
})

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

否则下面的代码足以验证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

When the JwtBearer middleware handles a request for the first time, it tries to retrieve some metadata from the authorization server (also called an authority or issuer). This metadata, or discovery document in OpenID Connect terminology, contains the public keys and other details needed to validate tokens. (Curious what the metadata looks like? Here’s an example discovery document.)

If the JwtBearer middleware finds this metadata document, it configures itself automatically. Pretty nifty!

这无疑是我去年不得不处理的最困难的代码。 “在 .NET Web API 应用程序中验证来自 AWS Cognito 的 JWT 令牌”。 AWS 文档仍有很多不足之处。

这是我用于新 .NET 6 Web API 解决方案的内容(因此 Startup.cs 现在包含在 Program.cs 中。调整以适应您的 .NET 版本(如果需要)。与 .NET 5 及更早版本的主要区别在于 Services 对象是通过名为 builder 的变量访问的,因此无论何时您看到类似 services.SomeMethod... 的代码,您可能可以将其替换为 builder.Services.SomeMethod... 以使其与 .NET 6 兼容):

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 未经授权的响应。 SO 上的其他人说他们必须这样做才能使 OAuth 的 Authentication/Authentication 代码授权类型起作用。显然 ValidateAudience = true 将适用于隐式授权。你为什么要在 2022 年使用隐性拨款?

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

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

至于控制器,class 级别的简单 [Authorize] 属性就足够了。无需在 [Authorize] 属性中将“Bearer”指定为 AuthenticationScheme,也无需创建中间件。

如果你想跳过必须向每个控制器添加另一个 using 以及 [Authorize] 属性,并且你希望每个控制器中的每个端点都需要一个 JWT,你可以把这个在 Startup/Program.cs:

builder.Services.AddControllers(opt =>
{
    var policy = new AuthorizationPolicyBuilder().RequireAuthenticatedUser().Build();
    opt.Filters.Add(new AuthorizeFilter(policy));
});

确保 Program.cs(Startup.cs 对于 .NET 5 及更早版本)app.UseAuthenticationapp.UseAuthorization().

之前

这是 Program.using.cs/Startup.cs:

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