【问题标题】:How to validate AWS Cognito JWT in .NET Core Web API using .AddJwtBearer()如何使用 .AddJwtBearer() 在 .NET Core Web API 中验证 AWS Cognito JWT
【发布时间】: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 文档严重缺乏这一事实,我决定发布此问答,以帮助其他人在未来更轻松地找到此解决方案。

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

【问题讨论】:

    标签: .net-core jwt amazon-cognito asp.net-core-webapi jwk


    【解决方案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>/…"
      猜你喜欢
      • 2021-06-11
      • 2020-06-04
      • 2017-03-11
      • 1970-01-01
      • 2021-10-03
      • 2020-12-12
      • 2021-06-27
      • 2018-03-07
      • 1970-01-01
      相关资源
      最近更新 更多