14

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

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

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

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

4

3 回答 3

31

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

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

一种方法是从方法内的 URL 获取和序列化列表IssuerSigningKeyResolver。整体.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库,可以通过观察结果在浏览器控制台中看到ValidIssuer和等参数ValidAudienceAuth.currentSession()

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

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

// 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))
})
于 2018-11-10T23:30:10.163 回答
6

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

否则,以下代码足以验证 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 中间件找到这个元数据文档,它会自动配置自己。相当漂亮!

于 2020-05-12T12:59:08.043 回答
3

这很容易成为我去年不得不使用的最困难的代码。“在 .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 Unauthorized 响应。SO上的其他人说他们必须这样做才能使OAuth的身份验证/身份验证代码授权类型起作用。显然ValidateAudience = true,对于隐式授权来说,效果很好。为什么你在 2022 年使用隐式授权呢?

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

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

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

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

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

确保 Program.cs(.NET 5 及更早版本的 Startup.cs)app.UseAuthentication中的app.UseAuthorization().

以下是usingProgram.cs/Startup.cs 中的 s:

using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.IdentityModel.Tokens;
于 2022-02-06T02:01:35.807 回答