IssuerSigningKeyResolver 调用异步方法

问题描述 投票:0回答:2

我们使用 IssuerSigningKeyResolver,它是 Microsoft.IdentityModel.Tokens 的一部分,用于令牌验证并接受非异步委托。我们调用一个异步方法,这将导致阻塞调用,所以想知道使用它的正确方法是什么。

IssuerSigningKeyResolver = (token, securityToken, identifier, parameters) =>
                {
                    return configurationManager.GetConfigurationAsync().ConfigureAwait(false).GetAwaiter().GetResult().SigningKeys;
                }
c# async-await jwt
2个回答
5
投票

我的 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 确认,该池的数据不应更改,因为当前池上没有密钥轮换策略..所以..

解决方案是在委托方法之外请求此数据并将其传入。

这有两个好处;

  1. 它解决了问题
  2. 效率更高。

此委托方法在每个请求上执行,因此我们每次都调用此 .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,因此我们尚未尝试更新版本)

希望这有帮助。


0
投票

我们通过使用

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;
            }
        };

这还有一个额外的好处,即添加对多个领域的支持。

© www.soinside.com 2019 - 2024. All rights reserved.