首页 文章

用于承载令牌认证的Owin中间件,支持JWT密钥轮换

提问于
浏览
2

我正在寻找配置owin中间件承载令牌身份验证以支持Open Id Connect密钥轮换的一些指导 .

Opend Id Connect spec表示以下关于键旋转的内容:

可以使用以下方法完成签名密钥的旋转 . 签名者在其jwks_uri位置的JWK集中发布其密钥,并在每个消息的JOSE标头中包括签名密钥的孩子,以向验证者指示将使用哪个密钥来验证签名 . 可以通过在jwks_uri位置定期向JWK Set添加新密钥来覆盖密钥 . 签名者可以自行决定开始使用新密钥,并使用kid值向验证者发出更改信号 . 验证者知道返回到jwks_uri位置以在看到不熟悉的孩子值时重新检索密钥 .

我能在这个问题上找到的最相似的问题是:SecurityTokenSignatureKeyNotFoundException in OWIN OpenID Connect middleware connecting to Google

解决方案不能正常工作,因为在发出新私钥和客户端刷新其公钥缓存之间会出现错误 .

因此,我想配置客户端,以便在找到有效且正确签名的非过期JWT令牌时下载丢失的公共JWK密钥,该JWT令牌具有不在本地缓存的子节点 .

我目前正在使用IdentityServer3.AccessTokenValidation但是当客户端收到一个无法识别的孩子的令牌时,客户端不会下载新密钥 .

我快速浏览了一下Microsoft.Owin.Security.Jwt - > UseJwtBearerAuthentication以及Microsoft.Owin.Security.OpenIdConnect - > UseOpenIdConnectAuthentication但我没有太过分 .

我正在寻找一些方向来扩展/配置任何上述软件包以支持密钥轮换 .

1 回答

  • 3

    我用system.IdentityModel.Tokens.Jwt库来计算它 . 我在版本控制方面遇到了很多麻烦,所以我已经包含了我最终使用的nuget包 . 我有很多问题与Microsoft.IdentityModel.Tokens.Jwt,所以我放弃了这种方法 . 无论如何这里是包:

    <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" />
    

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

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

    获取密钥的一些限制将有助于阻止恶意用户强制许多往返于令牌服务 .

相关问题