我们使用 IssuerSigningKeyResolver,它是 Microsoft.IdentityModel.Tokens 的一部分,用于令牌验证并接受非异步委托。我们调用一个异步方法,这将导致阻塞调用,所以想知道使用它的正确方法是什么。
IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
{
return configurationManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult().SigningKeys;
}
我的 asp.net core (3.1) webapi 也遇到了同样的问题,它使用 AWS Cognito 进行身份验证,使用的方法如下所述:
https://medium.com/@marcio_30193/jwt-machine-to-machine-usando-aws-cognito-and-c-b1fab0524712
这导致了类似于 @Punit 这样的公共代码块;
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
// Get JsonWebKeySet from AWS
var json = new WebClient().DownloadString($"{parameters.ValidIssuer}/.well-known/jwks.json");
// Deserialize the result
return JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
}
这段代码的问题是在asp.net core中这会导致线程池饥饿。
DownloadString 方法是同步的,但它内部执行 .Result 调用,导致线程阻塞,当请求激增时,这可能会导致线程池饥饿。
我使用 Ben.Blocking 检测器发现了这个阻塞调用 - 非常酷 https://github.com/benaadams/Ben.BlockingDetector https://www.nuget.org/packages/Ben.BlockingDetector/
那么如何解决呢?
我尝试过,但我认为在委托内部解决是不可能的。 IssuerSigningKeyResolver 是同步委托,无法接受异步响应。因此,即使 WebClient 有 DownloadStringAsyncTask,它也会将响应放入 IssuerSigningKeyResolver 不会接受的任务中。
简单的解决方案是跳出框框(或委托)思考。
使用 AWS Cognito,请求的数据位于如下 URL;
https://cognito-idp.{region}.amazonaws.com/{user-pool-id}/.well-known/jwks.json
这会返回 JsonWebKeySet 对象的集合,它表示一组加密密钥。
我已与 AWS 确认,该池的数据不应更改,因为当前池上没有密钥轮换策略..所以..
解决方案是在委托方法之外请求此数据并将其传入。
这有两个好处;
此委托方法在每个请求上执行,因此我们每次都调用此 .well-known/jwks URL,并且响应(每个环境)永远不会改变!!
我的代码看起来像这样;
var issuingKeys = GetIssuerSigningKey(AuthSettings.CognitoUserPoolUrl);
services.AddAuthentication(options =>
...
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
return issuingKeys;
}
...
);
private static IList<JsonWebKey> GetIssuerSigningKey(string cognitioUserPoolUrl)
{
var json = new WebClient().DownloadString($"{cognitioUserPoolUrl}/.well-known/jwks.json");
return JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
}
就我使用 AWS Cognito 的情况而言,这些数据存在更改的风险。 我的解决方案是启动一个带有 30 秒或 60 秒计时器的后台线程,然后在该时间间隔刷新静态 IssueKey。 如果确实发生变化,该网站将出现最多 30 秒的小故障,然后继续。
在@Punit的示例中,他从appSettings获取签名密钥,这100%不会改变,因此简单的修复它可以通过获取您的 在设置代码中的委托函数之外签名密钥并返回它。
解决此问题的唯一其他方法是将 IssuerSigningKeyResolver 转换为 IssuerSigningKeyResolverAsync(可能在更高版本的 .Net Core 中)。 (我们目前在 Lambda 中运行代码,目前仅支持 .net 3.1,因此我们尚未尝试更新版本)
希望这有帮助。
我们通过使用
ConcurrentDictionary
来缓存 JsonWebkeySet
来解决这个问题:
private static readonly ConcurrentDictionary<string, JsonWebKeySet> _keysetcache = new();
// ...
services
.AddAuthentication(JwtBearerDefaults.AuthenticationScheme).AddJwtBearer(options =>
{
options.TokenValidationParameters = new TokenValidationParameters
{
// ...
ValidateIssuerSigningKey = true,
IssuerSigningKeyResolver = (token, securityToken, kid, parameters) =>
{
return _keysetcache.GetOrAdd(kid, (k) =>
{
var realm = securityToken.Issuer.Split('/')[^1];
var baseurl = new Uri(new Uri("http://host.tld/realms/"), realm + "/");
return new JsonWebKeySet(DownloadKeysConfig(new Uri(baseurl, "protocol/openid-connect/certs")).Result);
}).Keys;
}
};
这还有一个额外的好处,即添加对多个领域的支持。