You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

Java Spring Boot下如何从Azure Key Vault提取RSA私钥?

解决方案:从Azure Key Vault获取RSA私钥并签名JWT

你的问题核心在于:当Azure Key Vault中的RSA密钥未设置为可导出(exportable=true),且应用不在可信执行环境(TEE)中时,无法直接获取私钥的明文材料——这是Azure KV的安全设计,避免私钥泄露。下面分两种场景给出可行方案:


场景1:密钥允许导出(架构允许私钥导出)

如果你的架构允许私钥导出,需要先确保密钥创建时开启了可导出选项:

  • 若通过Azure Portal创建密钥:在“创建密钥”页面,“可导出”选择“是”。
  • 若通过CLI/SDK创建:指定--exportable true(CLI)或在CreateKeyOptions中设置setExportable(true)。

修正后的代码如下,确保能正确获取可导出的私钥:

@Bean
public com.auth0.jwt.algorithms.Algorithm rsaAlgorithm(KeyClient azureKeyClient) {
    KeyVaultKey keyVaultKey = azureKeyClient.getKey(privateKey);
    JsonWebKey jsonWebKey = keyVaultKey.getKey();
    
    // 传入true强制获取私钥,前提是密钥已设置exportable=true
    KeyPair rsaPair = jsonWebKey.toRsa(true);
    RSAPublicKey publicKey = (RSAPublicKey) rsaPair.getPublic();
    RSAPrivateKey privateKeyRsa = (RSAPrivateKey) rsaPair.getPrivate();
    
    if (privateKeyRsa == null) {
        throw new IllegalStateException("无法获取私钥,请检查密钥是否设置为可导出(exportable=true)");
    }
    
    return Algorithm.RSA256(publicKey, privateKeyRsa);
}

场景2:密钥不可导出(符合架构要求,私钥不离开KV)

这是更安全的方案,不需要本地持有私钥,直接调用Azure Key Vault的签名服务完成JWT签名:

实现步骤

  1. 确保服务主体拥有Key Vault Crypto User角色权限,允许执行签名操作。
  2. 使用KeyClient调用KV的签名接口,传入JWT的待签名字符串。
  3. 用KV返回的签名结果组装完整JWT。

代码示例

@Service
public class JwtSignerService {
    private final KeyClient keyClient;
    private final String keyName;

    public JwtSignerService(KeyClient keyClient, @Value("${azure.keyvault.key.name}") String keyName) {
        this.keyClient = keyClient;
        this.keyName = keyName;
    }

    public String generateSignedJwt(Map<String, Object> customClaims, String issuer, String audience) throws JsonProcessingException {
        // 1. 构建JWT Header
        Map<String, Object> header = new HashMap<>();
        header.put("alg", "RS256");
        header.put("kid", keyName); // 使用KV密钥名称作为密钥标识

        // 2. 构建JWT Payload
        long currentTime = System.currentTimeMillis() / 1000;
        Map<String, Object> payload = new HashMap<>(customClaims);
        payload.put("iss", issuer);
        payload.put("aud", audience);
        payload.put("iat", currentTime);
        payload.put("exp", currentTime + 3600); // 1小时过期

        // 3. 序列化Header和Payload为Base64URL格式
        ObjectMapper objectMapper = new ObjectMapper();
        String base64Header = Base64.getUrlEncoder().withoutPadding().encodeToString(objectMapper.writeValueAsBytes(header));
        String base64Payload = Base64.getUrlEncoder().withoutPadding().encodeToString(objectMapper.writeValueAsBytes(payload));
        String signingInput = base64Header + "." + base64Payload;

        // 4. 调用Azure Key Vault执行签名
        SignResult signResult = keyClient.sign(
            keyName,
            SignatureAlgorithm.RS256,
            signingInput.getBytes(StandardCharsets.UTF_8)
        );

        // 5. 组装完整JWT
        String base64Signature = Base64.getUrlEncoder().withoutPadding().encodeToString(signResult.getSignature());
        return signingInput + "." + base64Signature;
    }
}

为什么你之前的代码会失败?

  • 当密钥未设置exportable=true且不在TEE环境时,Azure KV返回的JsonWebKey不会包含私钥的核心参数(如d、p、q等),因此toRsa()返回的私钥为null,toRsa(true)会因缺少必要参数抛出NPE。
  • TEE环境(如Azure机密计算虚拟机)是特殊场景,需要密钥在TEE中创建且应用运行在TEE内,才能获取私钥的内存实例(不会导出明文),但绝大多数业务场景不需要这种配置。

内容的提问来源于stack exchange,提问作者Wrapper

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.06.12 17:24:50