【问题标题】:Azure KeyVault - Sign JWT TokenAzure KeyVault - 签署 JWT 令牌
【发布时间】:2019-11-17 15:09:13
【问题描述】:

我开始使用 Azure Keyvault 来存储我的应用程序的私钥。

我有一个用例,我需要使用 RSA 私钥签署 JWT 令牌。

当我的应用程序内存中有私钥时,这很容易, 我会这样做

var token = new JwtSecurityToken(
                issuer,
                ...,
                claims,
                ...,
                ...,
                signingCredentials_PrivateKey);

现在我开始使用 Azure Keyvault,我想看看是否可以通过 KeyVaultClient.SignAsync 方法签署 JWT 令牌。

类似

KeyVaultClient client = ...;
var token = new JwtSecurityToken(
                issuer,
                ...,
                claims,
                ...,
                ...);
var tokenString = client.SignAsync(myKeyIdentifier, token);

【问题讨论】:

    标签: c# azure asp.net-core .net-core azure-keyvault


    【解决方案1】:

    首先,一个 JWT 令牌由三部分组成:Header、Payload 和 Signature。都是Base64UrlEncoded。

    你可以得到如下签名:

    HMAC-SHA256(
     base64urlEncoding(header) + '.' + base64urlEncoding(payload),
     secret
    )
    

    所以,你需要生成header和payload,将它们按点组合,计算hash,然后你就可以得到签名了。

    这是一个示例供您参考:

    var byteData = Encoding.Unicode.GetBytes(base64urlEncoding(header) + "." + base64urlEncoding(payload));
    var hasher = new SHA256CryptoServiceProvider();
    var digest = hasher.ComputeHash(byteData);
    var signature = await keyClient.SignAsync(keyIdentifier, "RS256", digest);
    var token = base64urlEncoding(header) + "." + base64urlEncoding(payload) + "." + base64urlEncoding(signature)
    

    SignAsync 的官方 SDK 文档

    JWT 的维基

    【讨论】:

    • 我找到了另一种解决方案,但最终使用了您的解决方案。看我的回答。
    • @user10962730 我在我身边测试,并获得了成功。请参阅我的新答案。
    【解决方案2】:

    我最终使用了 Jack Jia 的答案

    var token = new JwtSecurityToken(
                    issuer,
                    appId,
                    claims,
                    signDate,
                    expiryDate);
    
    var header = Base64UrlEncoder.Encode(JsonConvert.SerializeObject(new Dictionary<string, string>()
    {
        { JwtHeaderParameterNames.Alg, "RS256" },
        { JwtHeaderParameterNames.Kid, "https://myvault.vault.azure.net/keys/mykey/keyid" },
        { JwtHeaderParameterNames.Typ, "JWT" }
    }));
    var byteData = Encoding.UTF8.GetBytes(header + "." + token.EncodedPayload);
    var hasher = new SHA256CryptoServiceProvider();
    var digest = hasher.ComputeHash(byteData);
    var signature = await _keyVault.SignAsync("https://myvault.vault.azure.net/keys/mykey/keyid", "RS256", digest);
    
    return $"{header}.{token.EncodedPayload}.{Base64UrlEncoder.Encode(signature.Result)}";
    

    我找到了另一种解决方案,我不太喜欢它,但它与 JWT 库“集成”得更好。

    var token = new JwtSecurityToken(
        issuer,
        appId,
        claims,
        signDate,
        expiryDate,
        new SigningCredentials(new KeyVaultSecurityKey("https://myvault.vault.azure.net/keys/mykey/keyid", new KeyVaultSecurityKey.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback)), "RS256")
        {
            CryptoProviderFactory = new CryptoProviderFactory() { CustomCryptoProvider = new KeyVaultCryptoProvider() }
        });
    
    var handler = new JwtSecurityTokenHandler();
    return handler.WriteToken(token);
    

    原来有一个库 Microsoft.IdentityModel.KeyVaultExtensions 扩展为 SecurityTokenICryptoProvider 支持 KeyVault。

    我的问题是

    1. 我无法通过此解决方案重用 KeyVaultClient 的现有实例。
    2. 它正在阻塞(在幕后,它在KeyVaultClient.SignAsync 上调用.GetAwaiter().GetResult()

    【讨论】:

    • 我测试了您的解决方案。这是完美的。
    • @JackJia 我很好奇你是否研究过 Tore Nestenius 在他的回答中提到的限速因素?
    【解决方案3】:

    如果有人需要离线验证。我使用 user10962730 的答案来生成令牌,并使用以下内容进行离线验证:

    在线时,我们的客户端将从我们的 API 中检索公共信息。 RSA 模数和指数将被传输 Base64Url 编码而不是字节数组

    var keyBundle = await _keyVault.GetKeyAsync("https://myvault.vault.azure.net/keys/mykey/keyid");
    return new { n = keyBundle.N, e = keyBundle.E };
    

    那么当客户端需要验证一个token时

    string jwtToken = "[Header].[Payload].[Signature]";
    var jwtParts = jwtToken.Split(".");
    var rsa = new RSACryptoServiceProvider();
    var p = new RSAParameters() { Modulus = Base64UrlEncoder.DecodeBytes(key.n), Exponent = Base64UrlEncoder.DecodeBytes(key.e) };
    rsa.ImportParameters(p);
    var dataToHash = Encoding.UTF8.GetBytes($"{jwtParts[0]}.{jwtParts[1]}");
    byte[] digestBytes = SHA256.Create().ComputeHash(dataToHash);
    var sigBytes = Base64UrlEncoder.DecodeBytes(jwtParts[2]);
    var isVerified = rsa.VerifyHash(digestBytes, "Sha256", sigBytes);
    

    【讨论】:

      【解决方案4】:

      请注意,如果您让 Azure Key Vault 签署您的令牌,那么您需要注意一个速率限制因素。我认为最好从 AKV 下载私钥,然后在本地签署我的令牌。

      link了解更多信息

      【讨论】:

      • 这很好,但“更好”取决于几个因素。如果目标是保密私钥,那么更好的方法可能是限制您自己的客户端以响应来自 Key Vault 的 429 响应。如果最近有任何来自 Key Vault 的 429 响应,您还可以尝试使用断路器或高水位线模式来实现一些背压,您可以在向 Key Vault 发出实际请求之前发送自己的 429 响应。跨度>
      【解决方案5】:

      对于寻求不那么宽松的方法(也就是说,不使用 KeyVaultClient 并使用新的 Azure API)的任何人:

      https://docs.microsoft.com/en-us/dotnet/api/azure.security.keyvault.keys.cryptography.cryptographyclient?view=azure-dotnet 将允许您调用“SignDataAsync”方法,该方法接受 SignatureAlgorithm 和您的数据(以字节 [] 或流形式)和取消令牌。它返回一个 SignatureResult,可用于检索实际签名为 byte[] 和各种其他信息。

      此处将在“.”级联的 base64url 编码的标头和有效负载上调用 SignDataAsync(如 rfc7515 中所述)。

      【讨论】:

        猜你喜欢
        • 2021-11-06
        • 1970-01-01
        • 2018-03-11
        • 2019-08-27
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2021-07-03
        相关资源
        最近更新 更多