Pad*_*key 3 c# asp.net security bearer-token openid-connect
我正在寻找有关配置owin中间件承载令牌身份验证以支持Open Id Connect密钥轮换的指南。
在运行结束编号连接规范说,关于密钥轮换如下:
签名密钥的旋转可以通过以下方法完成。签名者将其密钥发布到位于其jwks_uri位置的JWK集中,并将签名密钥的孩子包含在每个消息的JOSE标头中,以向验证者指示要使用哪个密钥来验证签名。可以通过在jwks_uri位置将新密钥定期添加到JWK集中来滚动密钥。签名者可以根据自己的判断开始使用新密钥,并使用kid值将更改发送给验证者。验证者知道看到不熟悉的孩子值时,会知道返回jwks_uri位置以重新获取密钥。
我可以在此主题上找到最相似的问题是: OWIN OpenID Connect中间件中的SecurityTokenSignatureKeyNotFoundException连接到Google
该解决方案不太有效,因为在颁发新私钥到客户端刷新其公钥缓存之间,您会遇到错误。
因此,我希望将客户端配置为在找到有效,正确签名,未过期且其子代不在本地缓存的JWT令牌时下载丢失的公共JWK密钥。
我当前使用的是IdentityServer3.AccessTokenValidation,但是当客户端与无法识别的孩子一起使用令牌时,客户端不会下载新密钥。
我快速浏览了Microsoft.Owin.Security.Jwt-> UseJwtBearerAuthentication以及Microsoft.Owin.Security.OpenIdConnect-> UseOpenIdConnectAuthentication,但我没有走太远。
我正在寻找扩展/配置上述任何软件包以支持按键旋转的方向。
我使用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" />
Run Code Online (Sandbox Code Playgroud)
这是代码。它的工作方式是通过设置自定义密钥解析器。每次传递令牌时都会调用此密钥解析器。当我们遇到孩子缓存未命中时,我们会向令牌服务发出新请求,以下载最新的密钥集。最初,我考虑过首先检查密钥的各个部分(即未过期/有效的发行者),但后来决定反对,因为如果我们无法确认令牌是否正确签名,那么添加这些检查是没有意义的。攻击者可以将其设置为所需的任何内容。
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;
}
}
Run Code Online (Sandbox Code Playgroud)
围绕获取密钥进行一些限制对于阻止恶意用户强制多次访问令牌服务是有意义的。