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

nba*_*man 4 jwt amazon-cognito .net-core jwk asp.net-core-webapi

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

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

最后,尽管经过了很多随机的Google搜索和反复试验,我还是找到了一个(看似不是很有效的解决方案)解决方案。但是,我花了太多时间来做。出于这一点,再加上严重缺乏有关该主题的AWS文档这一事实,我决定发布此问答,以帮助其他人将来更轻松地找到此解决方案。

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

nba*_*man 11

答案主要在于正确定义TokenValidationParameters.IssuerSigningKeyResolver(此处显示的参数等:https : //docs.microsoft.com/zh-cn/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
                    };
                });
Run Code Online (Sandbox Code Playgroud)

如果您使用JS库,例如AWS放大,你可以看到参数,如ValidIssuerValidAudience在浏览器的控制台通过观察结果Auth.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.getIdToken().getJwtToken()}`,
      'Content-Type': 'application/json'
    }
  }
).then(response => response.json())
 .then(data => console.log(data))
 .catch(e => console.error(e))
})
Run Code Online (Sandbox Code Playgroud)

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

jsp*_*lla 10

这无疑是我去年处理过的最困难的代码。“在 .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 here}",
            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";
    });
Run Code Online (Sandbox Code Playgroud)

请注意,我已ValidateAudience设置为false. 否则,我会从 .NET 应用程序收到 401 未经授权的响应。SO 上的其他人表示他们必须这样做才能使 OAuth 的身份验证/身份验证代码授予类型正常工作。显然,ValidateAudience = true对于隐式授予来说效果很好,但是大多数人认为隐式授予不推荐使用,如果可能的话,您应该尽量避免它。

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

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

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

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

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

确保 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;
Run Code Online (Sandbox Code Playgroud)

  • 此外,ValidateAudiance 无法正常工作,因为您使用的“AccessToken”不包含“aud”声明。为了验证受众,您需要使用“IdToken”。 (2认同)

jfl*_*net 5

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

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

services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddJwtBearer(options =>
{
    options.Authority = "{yourAuthorizationServerAddress}";
    options.Audience = "{yourAudience}";
});
Run Code Online (Sandbox Code Playgroud)

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

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

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