我们的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) =>
{
var json = new WebClient().DownloadString($"{parameters.ValidIssuer}/.well-known/jwks.json");
return JsonConvert.DeserializeObject<JsonWebKeySet>(json).Keys;
}
这段代码的问题在于,在asp.net core中,它会导致线程池饥饿。
DownloadString方法是同步的,但内部执行了.Result调用,导致线程阻塞,当请求激增时,可能会导致线程池饥饿。
我使用了Ben.Blocking detector找到了这个阻塞调用 - 非常酷
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获取signingKeys,这100%不会更改,因此简单的解决方法是通过在设置代码中委托函数之外获取您的签名密钥并返回它来避免问题。
修复这个问题的唯一其他方法是将IssuerSigningKeyResolver转换为IssuerSigningKeyResolverAsync,可能在稍后的.Net Core版本中。(我们目前在Lambda中运行我们的代码,它只支持.net 3.1,所以我们尚未尝试更新版本)
希望对你有所帮助。