当前位置: 首页 > 知识库问答 >
问题:

用于支持JWT密钥轮换的承载令牌身份验证的Owin中间件

公西俊德
2023-03-14

我正在寻找一些指导,配置owin中间件承载令牌身份验证,以支持Open Id Connect密钥旋转。

Opend Id Connect规范说明了以下有关关键点旋转的内容:

签名密钥的旋转可以通过以下方法完成。签名者在其jwks_uri位置的JWK集中发布其密钥,并在每条消息的JOSE头中包含签名密钥的kid,以向验证者指示将使用哪个密钥验证签名。可以通过定期向jwks_uri位置的JWK集合添加新键来滚动键。签名者可以自行决定开始使用新密钥,并使用kid值向验证器发送更改信号。当验证器看到一个不熟悉的kid值时,它知道返回jwks_uri位置重新检索密钥。

关于这个问题,我能找到的最相似的问题是:连接到Google的OWIN OpenID Connect中间件中的SecurityTokenSignatureKeyNotFoundException

该解决方案不太管用,因为在发出新私钥和客户端刷新其公钥缓存之间,您会遇到错误。

因此,我想将客户端配置为在发现有效、正确签名、未过期的JWT令牌时下载丢失的公共JWK密钥,该令牌包含未在本地缓存的kid。

我目前正在使用标识符服务器3。访问令牌验证,但当客户端用它不认识的孩子回收令牌时,它不会下载新密钥。

我快速浏览了一下微软。Owin.安全。Jwt-

我正在寻找一些方向来扩展/配置上述任何包,以支持键旋转。

共有1个答案

融修平
2023-03-14

我用这个系统算出了。IdentityModel。代币。Jwt图书馆。我在版本控制方面遇到了很多麻烦,所以我包括了我最终使用的nuget包。我和微软有很多问题。IdentityModel。代币。所以我放弃了这种方法。无论如何,以下是软件包:

<package id="Microsoft.IdentityModel.Protocol.Extensions" version="1.0.2.206221351" targetFramework="net462" />
<package id="Microsoft.Win32.Primitives" version="4.0.1" targetFramework="net462" />
<package id="System.IdentityModel.Tokens.Jwt" version="4.0.2.206221351" targetFramework="net462" />
<package id="System.Net.Http" version="4.1.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Algorithms" version="4.2.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Encoding" version="4.0.0" targetFramework="net462" />
<package id="System.Security.Cryptography.Primitives" version="4.0.0" targetFramework="net462" />
<package id="System.Security.Cryptography.X509Certificates" version="4.1.0" targetFramework="net462" />

这是代码。其工作方式是设置自定义密钥解析程序。每次传入令牌时,都会调用此密钥解析程序。当我们得到一个kid缓存未命中时,我们向令牌服务发出一个新请求,以下载最新的密钥集。起初,我想先检查密钥的各个部分(即未过期/有效的颁发者),但后来决定不这样做,因为如果我们无法确认令牌已正确签名,那么添加这些检查是毫无意义的。攻击者可以设置他们想要的任何内容。

using Microsoft.IdentityModel.Protocols;
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net.Http;
using System.Security.Cryptography;
using System.Threading;
using System.Threading.Tasks;

public class ValidationMiddleware
{
    private readonly Func<IDictionary<string, object>, Task> next;
    private readonly Func<string> tokenAccessor;
    private readonly ConfigurationManager<OpenIdConnectConfiguration> configurationManager;

    private readonly Object locker = new Object();
    private Dictionary<string, SecurityKey> securityKeys = new Dictionary<string, SecurityKey>();

    public ValidationMiddleware(Func<IDictionary<string, object>, Task> next, Func<string> tokenAccessor)
    {
        this.next = next;
        this.tokenAccessor = tokenAccessor;

        configurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(
            "url to open id connect token service", 
            new HttpClient(new WebRequestHandler()))
        {
            // Refresh the keys once an hour
            AutomaticRefreshInterval = new TimeSpan(1, 0, 0)
        };
    }

    public async Task Invoke(IDictionary<string, object> environment)
    {
        var token = tokenAccessor();

        var validationParameters = new TokenValidationParameters
        {
            ValidAudience = "my valid audience",
            ValidIssuer = "url to open id connect token service",
            ValidateLifetime = true,
            RequireSignedTokens = true,
            RequireExpirationTime = true,
            ValidateAudience = true,
            ValidateIssuer = true,
            IssuerSigningKeyResolver = MySigningKeyResolver, // Key resolver gets called for every token
        };

        JwtSecurityTokenHandler.InboundClaimTypeMap.Clear();

        var tokenHandler = new JwtSecurityTokenHandler(); 
        var claimsPrincipal = tokenHandler.ValidateToken(token, validationParameters, out SecurityToken validatedToken);

        // Assign Claims Principal to the context.

        await next.Invoke(environment);
    }

    private SecurityKey MySigningKeyResolver(string token, SecurityToken securityToken, SecurityKeyIdentifier keyIdentifier, TokenValidationParameters validationParameters)
    {
        var kid = keyIdentifier.OfType<NamedKeySecurityKeyIdentifierClause>().FirstOrDefault().Id;

        if (!securityKeys.TryGetValue(kid, out SecurityKey securityKey))
        {
            lock (locker)
            {
                // Double lock check to ensure that only the first thread to hit the lock gets the latest keys.
                if (!securityKeys.TryGetValue(kid, out securityKey))
                {
                    // TODO - Add throttling around this so that an attacker can't force tonnes of page requests.

                    // Microsoft's Async Helper
                    var result = AsyncHelper.RunSync(async () => await configurationManager.GetConfigurationAsync());

                    var latestSecurityKeys = new Dictionary<string, SecurityKey>();
                    foreach (var key in result.JsonWebKeySet.Keys)
                    {
                        var rsa = RSA.Create();
                        rsa.ImportParameters(new RSAParameters
                        {
                            Exponent = Base64UrlEncoder.DecodeBytes(key.E),
                            Modulus = Base64UrlEncoder.DecodeBytes(key.N),
                        });
                        latestSecurityKeys.Add(key.Kid, new RsaSecurityKey(rsa));

                        if (kid == key.Kid)
                        {
                            securityKey = new RsaSecurityKey(rsa);
                        }
                    }

                    // Explicitly state that this assignment needs to be atomic.
                    Interlocked.Exchange(ref securityKeys, latestSecurityKeys);
                }
            }
        }

        return securityKey;
    }
}

对获取密钥进行一些限制对于阻止恶意用户强制多次往返令牌服务是有意义的。

 类似资料:
  • 我试图用PHP为两个主题相连的领域创建一个简单的SSO系统。 因此,我想知道是否可以将包含用户用户名的签名JWT令牌从域a存储到本地存储。然后使用来自域B的相同密钥来验证JWT,这将导致成功的身份验证。 我在谷歌搜索了一些答案,我发现其中一些包含了一个中间认证域,它将负责认证。但我只想把我有的两个域联系起来。 谢了。

  • 我读了一些关于“JWT vs Cookie”的帖子,但它们只会让我更加困惑…… > 我想澄清一下,当人们谈论“基于令牌的身份验证与cookie”时,这里的cookie仅指会话cookie?我的理解是,cookie就像一个介质,它可以用来实现基于令牌的身份验证(在客户端存储可以识别登录用户的东西)或者基于会话的身份验证(在客户端存储与服务器端会话信息匹配的常量) 为什么我们需要JSON web令牌?

  • 我正在尝试在ASP.NET5中实现OAuth承载令牌身份验证,并且正在努力寻找一个如何实现这一点的示例,因为OWIN的东西在ASP.NET5中发生了变化。 例如IApplicationBuilder.UseOAuthAuthorizationServer()和IApplicationBuilder。UseOAuthBearerAuthentication()要么不再存在,要么缺少引用? 如有任何指

  • 如何在springdoc-openapi-ui(openapi3.0)中启用“authorize”按钮以进行承载令牌身份验证,例如JWT。 必须向Spring和类添加哪些注释?

  • 是否可以在ASP中支持多个JWT令牌发行者。净核心2?我想为外部服务提供一个API,我需要使用两个JWT代币来源——Firebase和定制JWT代币发行人。在ASP。NET core I可以为承载身份验证方案设置JWT身份验证,但只能为一个机构设置: 我可以有多个发行人和受众,但我不能设置多个权限。

  • 我询问了如何建立一个服务呼叫,并在HttpClient上获得了一个很好的信息。然而,虽然这个问题在技术上得到了回答,但我还是被卡住了。 在控制台中,我可以看到我的浏览器向服务发送了什么请求来获取授权令牌。然而,当我尝试在我的服务层中模拟构建请求的调用时,我得到以下错误消息。我在这里犯错的可能性很大。不知道该用谷歌搜索什么,真的。。。 "StatusCode: 500, ReasonPhrase:'