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

19

我遇到了一些困难,不知道如何在我的.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关于此主题的文档严重缺乏,我决定发布这篇问答以帮助其他人更轻松地找到这个解决方案。

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

3个回答

39

答案主要在于正确定义TokenValidationParameters.IssuerSigningKeyResolver(该参数的详情可参见此处:https://learn.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

一个REST fetch请求从JS客户端到.NET Core Web API,利用上述JWT身份验证,并在您的控制器上使用[Authorize]标记,可能会如下所示:

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

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

2
很棒的回答。在客户端,Amplify已经有了一些改变,所以现在我们应该使用user.signInUserSession.accessToken.jwtToken来获取令牌 :) - Dina
3
在2021年(核心3.1)中,JsonConvert.DeserializeObject<JsonWebKeySet>(json)可能无法正常工作 - 请改用此方法:new JsonWebKeySet(json) - YS.
3
这个例子创建了一个新的WebClient,并为每个经过身份验证的请求重新下载键。您可以设置JwtBearerOptions上的元数据地址,它会处理为您获取键和缓存。例如,options.MetadataAddress =" https://cognito-idp.<region>.amazonaws.com/<pool>/.well-known/openid-configuration" - Michael Petito

10

这是我在过去一年中遇到的最困难的代码,"在.NET Web API应用程序中验证来自AWS Cognito的JWT令牌"。 AWS文档仍然有许多需要改进之处。

以下是我在新的.NET 6 Web API解决方案中使用的(因此Startup.cs现在包含在Program.cs中。如有需要,请进行调整以适应您的.NET版本。与.NET 5和更早版本的主要区别在于通过名为 builder 的变量访问 Services 对象,因此每当您看到像 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";
    });
请注意,我将ValidateAudience设置为false。否则,我会从.NET应用程序中得到401未经授权的响应。 StackOverflow上的其他人说他们不得不这样做才能使OAuth的身份验证/身份验证代码授权类型起作用。显然,对于隐式授权,ValidateAudience = true也可以正常工作,但是隐式授权被大多数人视为已弃用,如果可能,您应该尝试避免使用它。
还要注意,我正在设置options.MetadataAddress。根据另一个StackOverflow用户的说法,这显然允许AWS在必要时从后台缓存签名密钥,而AWS定期更换这些密钥。
一些官方的AWS文档(嘘)误导了我,让我使用了builder.Services.AddCognitoIdentity();(.NET 5及更早版本使用services.AddCognitoIdentity();)。显然,这是用于“ASP.NET”应用程序的情况,其中后端提供前端(例如Razor / Blazor)。或者它已经过时了,谁知道呢。这在AWS的网站上,因此它很可能已被弃用...
至于控制器,仅在类级别添加一个简单的[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(对于 .NET 5 及更早版本的应用程序是 Startup.cs)中 app.UseAuthorization() 之前使用 app.UseAuthentication

以下是 Program.cs/Startup.cs 中的 using

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

1
谢谢!今天救了我一些严重的头痛! - Kaizen Programmer
1
经过一天的测试和搜索互联网,我找到了解决services.AddCognitoIdentity();的方法。这不是已弃用,但默认使用的身份验证方案是cookie。要覆盖它,您需要添加AddAuthentication(options => { options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme; options.DefaultScheme = JwtBearerDefaults.AuthenticationScheme; })。但我必须同意有关AWS文档的评论。 - ciprianp
没错。有没有什么理由要使用我回答中的代码而不是AddCognitoIdentity() / 反之亦然? - jspinella
1
使用 AddCognitoIdentity() 包含了使用 Asp.Net Identity 所需的样板代码。这意味着你可以使用 CognitoUserManager<CognitoUser>,它又有一些预先构建的函数来操作用户,并节省您一些时间。 - ciprianp
2
同时,ValidateAudience也无法工作,因为您正在使用不包含“aud”声明的AccessToken。为了验证受众,您需要使用IdToken。 - ciprianp
1
文档中可以了解到,访问令牌的目的是在用户池中授权API操作的上下文中授权用户。例如,您可以使用访问令牌来授予用户访问权限以添加、更改或删除用户属性。而身份验证令牌也可用于将用户身份验证到资源服务器或服务器应用程序。您还可以在Web API操作中使用身份验证令牌。 - ciprianp

8

只有当您需要更精细的控制验证时,才需要提供此处提供的答案。

否则,以下代码足以验证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术语中的发现文档(discovery document),包含验证令牌所需的公钥和其他详细信息。(想知道元数据长什么样?请看这里的示例发现文档。)

如果JwtBearer中间件找到了该元数据文档,则会自动配置自身。非常方便!


1
感谢您的回答!当我在2018年尝试使用您描述的方法与AWS Cognito和Amplify一起工作时,我无法使其正常运行。然而,我已经成功地多次使用JWT进行了自己的身份验证和用户管理实现。不过,如果现在这个方法可行,那真是太好了! - foxtrotuniform6969
这个能处理密钥集的旋转吗? - abhim

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接