如何使用 .AddJwtBearer() 在 .NET Core Web API 中验证 AWS Cognito JWT
Posted
技术标签:
【中文标题】如何使用 .AddJwtBearer() 在 .NET Core Web API 中验证 AWS Cognito JWT【英文标题】:How to validate AWS Cognito JWT in .NET Core Web API using .AddJwtBearer() 【发布时间】: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 文档严重缺乏这一事实,我决定发布此问答,以帮助其他人在未来更轻松地找到此解决方案。
如果有更好的方法可以做到这一点,请告诉我,因为除了下面列出的答案之外,我还没有找到方法。
【问题讨论】:
【参考方案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.UseAuthentication
在 app.UseAuthorization()
之前。
这里是 Startup.cs 或 Program.cs 中的using
s:
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()
的结果在浏览器控制台中看到ValidIssuer
和ValidAudience
等参数
利用上面实现的 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<JsonWebKeySet>(json)
可能无法使用 - 请改用:new JsonWebKeySet(json)
此示例创建一个新的 WebClient 并为每个经过身份验证的请求再次下载密钥。您可以改为在 JwtBearerOptions 上设置元数据地址,它将为您处理获取密钥和缓存。例如,`options.MetadataAddress = "cognito-idp.<region>.amazonaws.com/<pool>/…"以上是关于如何使用 .AddJwtBearer() 在 .NET Core Web API 中验证 AWS Cognito JWT的主要内容,如果未能解决你的问题,请参考以下文章
如何在 .NET Core 3.0 中替换 AddJwtBearer 扩展
如何在 Asp.Net Core 2.0“AddJwtBearer”中间件中设置多个受众?
ASP.NET Core - AddJwtBearer - 授权 URL,它是如何工作的?