如何使用 JwtSecurityTokenHandler 和 JWKS 端点验证 JWT?

How do I validate a JWT using JwtSecurityTokenHandler and a JWKS endpoint?

我正在设计使用 IdentityServer4 来保护多个服务的原型,但需要注意的是,这些服务可能不会迁移(在可预见的未来)以使用 ASP.NET Core 的 OWIN 中间件习惯用法。因此,我无法通过简单地提供众所周知的 IdentityServer 的 JWKS 端点等来利用许多自动验证 JWT 的中间件助手。

如果我能重建这种行为就好了,如果可能的话,我想利用 Microsoft 的 JwtSecurityTokenHandler 实现。但是,我不知道如何利用 IdentityServer 的发现端点提供的 JsonWebKeySetJsonWebKey 类型来提取密钥并执行验证。

JwtSecurityTokenHandler 使用 TokenValidationParameters to validate a JWT, and those parameters require an instance of one or more SecurityKey 个对象来执行验证。

ClaimsPrincipal ValidateJwt(string token, IdentityModel.Client.DiscoveryResponse discovery)
{
    JwtSecurityToken jwt = new JwtSecurityToken(token);

    TokenValidationParameters validationParameters = new TokenValidationParameters
    {
        ValidateAudience = true,
        ValidateIssuer = true,
        RequireSignedTokens = true,
        ValidIssuer = "expected-issuer",
        ValidAudience = "expected-audience",
        IssuerSigningKeys = discovery.KeySet.Keys /* not quite */
    };

    JwtSecurityTokenHandler handler = new JwtSecurityTokenHandler();
    SecurityToken validatedToken;
    return handler.ValidateToken(jwt, validationParameters, out validatedToken);
}

如何执行从 JsonWebKeySetIEnumerable<SecurityKey> 的必要转换以便进行验证?是否有另一种方法(除了 OWIN 中间件)也可以使用上面的 DiscoveryResponse 数据?

(遗憾的是,System.IdentityModel.Tokens.Jwt 的文档不是最新的。)

检查此示例:

https://github.com/IdentityServer/IdentityServer4/blob/master/samples/Clients/old/MvcManual/Controllers/HomeController.cs#L148

它从 JWK 手动检索密钥并填充验证参数。

var jwks = "{ keys: [..." // your jwks json string
var signingKeys = new JsonWebKeySet(jwks).GetSigningKeys();

然后只需将它分配给您的 TokenValidationParametersIssuerSigningKeys 属性。

如果您从 Web 服务读取 jwks,那么您首先需要一个 http 客户端来读取它。

使用包中的代码:

  • IdentityModel
  • System.IdentityModel.Tokens.Jwt

进口:

open IdentityModel
open IdentityModel.Client
open System.Security.Cryptography
open System.IdentityModel.Tokens.Jwt
open Microsoft.IdentityModel.Tokens

JsonWebKeySecurityKey 的转换:

module JsonWebKey =

  let toSecurityKey (webKey : Jwk.JsonWebKey) =
    let e = Base64Url.Decode(webKey.E)
    let n = Base64Url.Decode(webKey.N)

    let mutable rsap = RSAParameters()

    rsap.Exponent <- e
    rsap.Modulus <- n

    let key = RsaSecurityKey(rsap)

    key.KeyId <- webKey.Kid

    key :> SecurityKey

然后验证令牌:

task {
  let cache = new DiscoveryCache(oidConfigUrl)

  let! ddr = cache.GetAsync()

  let tokenHandler = JwtSecurityTokenHandler()

  let validationParameters = TokenValidationParameters()

  let issuerSigningKeys =
    ddr.KeySet.Keys
    |> Seq.map JsonWebKey.toSecurityKey
    |> Seq.toList

  validationParameters.IssuerSigningKeys <- issuerSigningKeys
  validationParameters.ValidIssuer <- ddr.Issuer
  validationParameters.ValidAudience <- "account"
  validationParameters.RequireSignedTokens <- true
  validationParameters.ValidateIssuer <- true
  validationParameters.ValidateLifetime <- true
  validationParameters.ValidateAudience <- true

  let! validationResult = 
    tokenHandler.ValidateTokenAsync(accessToken, validationParameters)

  printfn $"IsValid: {validationResult.IsValid}"
  printfn $"Exception: {validationResult.Exception}"
  printfn $"Claims: {validationResult.Claims |> Seq.toList}"
  printfn $"ClaimsIdentity: {validationResult.ClaimsIdentity.ToString()}"
}