我们正在尝试验证由 OpenID 连接提供程序 (OP) 提供给 .NET 客户端应用程序的 ID 令牌 (IDT)。IDT 是您所期望的。那里没有什么不寻常的事情发生。
为了验证 IDT 的签名,我们可以通过调用公共端点从 OP 获取指数和模数。这些可用于创建与 OP 用于签署 IDT 的私钥相对应的公钥。有了这些,我们创建了一个 RSACryptoServiceProvider 对象来进行签名验证。为了解决这个问题,我们将加密服务提供者作为令牌验证参数传递给 JwtSecurityTokenHandler。
这工作正常。我们以为我们已经完成并准备好迎接周末了。但是,我们发现我们可以更改签名中的最后一个字符,JwtSecurityTokenHandler 仍然会告诉我们 JWT 是有效的。我们找不到对此的解释,并想知道是否:
- 我们创建签名密钥的方式存在问题,导致它无法正确验证 JWT。
- JwtSecurityTokenHandler 中有一个错误。
- 我们不完全理解规范,允许进行这种小改动,因为 JWT 签名部分的最后一个字符实际上与验证无关。
- 别的东西
我们正在使用 System.IdentityModel.Tokens.Jwt.dll v4.0.30319 中的 System.IdentityModel.Tokens.JwtSecurityTokenHandler。
下面是我们代码的一个非常简单的示例。
程序.cs
using System;
using System.Configuration;
using System.IdentityModel.Tokens;
using System.Text;
namespace ConsoleApplication1
{
class Program
{
static void Main(string[] args)
{
var token = "eyJhbGciOiJSUzI1NiIsImtpZCI6IjEifQ.eyJzdWIiOiJ1c2VyMSIsImF1ZCI6ImNsaWVudDEiLCJqdGkiOiJKcUFDVVFiTlRQR201U0ZJRXY3MWR0IiwiaXNzIjoiaHR0cHM6XC9cL2xvY2FsaG9zdDo5MDMxIiwiaWF0IjoxNDEzNTcwNjEyLCJleHAiOjE0MTM1NzA5MTJ9.Z3P4Rt_w7d0oP8x6zfaot8PIxpEJHUw43Z_4VkOzv59nRz1dWopGUXw51DJd5cLjeM_zc14durs5NhJE27WmcKaEuE8-WZ0ubxM_bzykZfmAPa1WVk9KctPKiUH7QZg4OCLaqIX6usi5kkuICiPVdoJPkHmojMkm5nCqeBIbYteasysMTQGq93VtoBGUQomF89ZaFMBlUy0ofH7SEKJEW_4vgy7Umu0h7kNKkh6Aw4x9Bw1AkG1D6H_scsuH2uSxQ7QV-3G60DcjLZ31_R1ZxaUg2WS2ajemb6swKM4LIOR9_mK6ScUVVBxBL4Oh9g6EA93lMg_1GRZi780v_3TR8Q";
var tokenValidator = new TokenValidator(new CacheProvider(), new DebugOpenIdConnectProviderClient(),
ConfigurationManager.AppSettings["AUDIENCE"], ConfigurationManager.AppSettings["ISSUER"]);
SecurityToken securityToken;
var principal = tokenValidator.Validate(token, out securityToken);
if (principal != null)
{
Console.Out.WriteLine("Security token is valid");
}
foreach (var claim in principal.Claims)
{
Console.Out.WriteLine("{0} = {1}", claim.Type, claim.Value);
}
Console.ReadLine();
}
}
}
TokenValidator.cs
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Security.Claims;
using System.Security.Cryptography;
using Newtonsoft.Json;
namespace ConsoleApplication1
{
public class TokenValidator
{
private readonly CacheProvider cacheProvider;
private readonly IOpenIdConnectProviderClient openIdConnectProviderClient;
private readonly string audience;
private readonly string issuer;
public TokenValidator(CacheProvider cacheProvider, IOpenIdConnectProviderClient openIdConnectProviderClient, string audience, string issuer)
{
this.cacheProvider = cacheProvider;
this.openIdConnectProviderClient = openIdConnectProviderClient;
this.audience = audience;
this.issuer = issuer;
}
public ClaimsPrincipal Validate(string tokenString, out SecurityToken securityToken)
{
var jwtSecurityTokenHandler = new JwtSecurityTokenHandler();
var jwt = jwtSecurityTokenHandler.ReadToken(tokenString) as JwtSecurityToken;
var publicKey = GetPublicKey(jwt.Header.SigningKeyIdentifier[0].Id);
var rsaPublicKey = CreatePublicKey(publicKey.n, publicKey.e);
return jwtSecurityTokenHandler.ValidateToken(tokenString, new TokenValidationParameters()
{
IssuerSigningToken = new RsaSecurityToken(rsaPublicKey, publicKey.kid),
IssuerSigningKeyResolver = (token, securityToken2, keyIdentifier, validationParameters) => {
return new RsaSecurityKey(rsaPublicKey);
},
#if DEBUG
ClockSkew = new TimeSpan(0, 30, 0),
#endif
ValidIssuer = issuer,
ValidAudience = audience,
}, out securityToken);
}
public static RSACryptoServiceProvider CreatePublicKey(string modulus, string exponent)
{
var cryptoProvider = new RSACryptoServiceProvider();
cryptoProvider.ImportParameters(new RSAParameters()
{
Exponent = Base64UrlEncoder.DecodeBytes(exponent),
Modulus = Base64UrlEncoder.DecodeBytes(modulus),
});
return cryptoProvider;
}
private PublicKeyData GetPublicKey(string kid)
{
var keys = cacheProvider["PUBLIC_KEYS"] as Dictionary<string, PublicKeyData>;
if (keys == null)
{
keys = GetPublicKeysFromPingFederate();
cacheProvider["PUBLIC_KEYS"] = keys;
}
var currentKey = keys[kid];
if (currentKey != null)
{
return currentKey;
}
throw new Exception("Could not find public key for kid: " + kid);
}
private Dictionary<string, PublicKeyData> GetPublicKeysFromPingFederate()
{
var keyString = openIdConnectProviderClient.Execute();
var keys = JsonConvert.DeserializeObject<PublicKeysJsonResult>(keyString);
var result = new Dictionary<string, PublicKeyData>();
foreach (var key in keys.Keys)
{
result[key.kid] = key;
}
return result;
}
}
}