Azure KeyVault - 签署 JWT 令牌

Posted

技术标签:

【中文标题】Azure KeyVault - 签署 JWT 令牌【英文标题】:Azure KeyVault - Sign JWT Token 【发布时间】: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);

【问题讨论】:

【参考方案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。

我的问题是

    我无法通过此解决方案重用 KeyVaultClient 的现有实例。 它正在阻塞(在幕后,它在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 中所述)。

【讨论】:

以上是关于Azure KeyVault - 签署 JWT 令牌的主要内容,如果未能解决你的问题,请参考以下文章

从Xamarin表单访问Azure KeyVault

使用 Azure.Security.KeyVault.Secrets 的网络核心密钥保管库配置

Azure Keyvault 通过 ARM 添加 Function MSI

使用 KeyVault 和 Azure PowerShell 证书身份验证创建 Azure AD 应用程序

Azure 函数和 Azure KeyVault 通过服务端点进行通信

Azure KeyVault:Azure.Identity.CredentialUnavailableException:DefaultAzureCredential 无法从包含的凭据中检索令牌