我使用 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;
}
}
对获取密钥进行一些限制对于阻止恶意用户强制多次往返令牌服务是有意义的。