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

本地Spring Boot应用访问Azure Key Vault遇401错误及token使用困惑

问题:本地Spring Boot应用访问Azure Key Vault报401未授权错误

本地Spring Boot应用尝试通过Azure SDK读取Azure Key Vault中的密钥,使用以下代码:

@Component
public class KeyVaultHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultHelper.class);
    
    public SecretClient getSecretClient() {
        return new SecretClientBuilder()
                .vaultUrl("https://mba-key-vault-int.vault.azure.net/")
                .credential(new ClientSecretCredentialBuilder()
                        .tenantId("de8e2ba9-95c1-4fbb-b558-6bf8bb1d8081")
                        .clientId("b74bcf37-n044-4242-9187-d85b486379c1")
                        .clientSecret("mU48Q~LpWJL7EZDpTzjiNDzqVywcmseUKaR62b5P")
                        .build())
                .buildClient();
    }


    public String saveSecretToFile() {
        String secretValue = getSecretClient().getSecret("secretName").getValue();
        System.out.println("secret name", secretValue);
    }
}

调用saveSecretToFile()时抛出异常:

com.azure.security.keyvault.secrets.implementation.models.KeyVaultErrorException: Status code 401, "{"error":{"code":"Unauthorized","message":"AKV10000: Request is missing a Bearer or PoP token."}}"

已配置服务主体,并且能通过以下代码获取Access Token,但不清楚如何将该Token应用到Key Vault访问代码中:

public String getAccessToken() { 
      MultiValueMap<String,String> formData = prepareFormData();
      ResponseEntity<JsonNode> response = restTemplate.postForEntity(
      String.format("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId),
      httpEntity(formData),JsonNode.class); 
      JsonNode jsonNode=response.getBody(); 
      return jsonNode.get("access_token").asText(); 
}

MultiValueMap<String, String> prepareFormData() {
    MultiValueMap<String, String> map = new LinkedMultiValueMap<>();
    map.add("grant_type", "client_credentials");
    map.add("client_id", "b74bcf37-n044-4242-9187-d85b486379c1");
    map.add("client_secret", "mU48Q~LpWJL7EZDpTzjiNDzqVywcmseUKaR62b5P");
    map.add("scope", "https://vault.azure.net/.default");
    return map;
}

HttpEntity<MultiValueMap<String, String>> httpEntity(MultiValueMap<String, String> map) {
    HttpHeaders httpHeaders = new HttpHeaders();
    httpHeaders.setContentType(MediaType.APPLICATION_FORM_URLENCODED);
    httpHeaders.add("Accept", MediaType.APPLICATION_JSON_VALUE);
    HttpEntity<MultiValueMap<String, String>> request = new HttpEntity<>(map, httpHeaders);
    return request;
}

解决方案

方案一:修复SDK自带Credential的使用(推荐)

原代码中使用的ClientSecretCredential是Azure SDK官方提供的凭证实现,能自动管理Token的获取与刷新,出现401错误大概率是权限配置或依赖问题,按以下步骤排查修复:

1. 确认服务主体权限

进入Azure Portal的Key Vault资源,在访问策略中添加对应服务主体(client_id对应的应用),并授予Secrets Get权限,保存配置。

2. 确保依赖版本兼容

在pom.xml中使用兼容的Azure SDK版本(避免版本不匹配导致的认证问题):

<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-security-keyvault-secrets</artifactId>
    <version>4.7.0</version>
</dependency>
<dependency>
    <groupId>com.azure</groupId>
    <artifactId>azure-identity</artifactId>
    <version>1.12.0</version>
</dependency>

3. 优化代码(单例创建SecretClient)

避免每次调用都创建新的SecretClient,改为构造注入单例:

@Component
public class KeyVaultHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultHelper.class);
    private final SecretClient secretClient;

    public KeyVaultHelper() {
        this.secretClient = new SecretClientBuilder()
                .vaultUrl("https://mba-key-vault-int.vault.azure.net/")
                .credential(new ClientSecretCredentialBuilder()
                        .tenantId("de8e2ba9-95c1-4fbb-b558-6bf8bb1d8081")
                        .clientId("b74bcf37-n044-4242-9187-d85b486379c1")
                        .clientSecret("mU48Q~LpWJL7EZDpTzjiNDzqVywcmseUKaR62b5P")
                        .build())
                .buildClient();
    }

    public String saveSecretToFile() {
        String secretValue = secretClient.getSecret("secretName").getValue();
        LOGGER.info("Retrieved secret value: {}", secretValue);
        return secretValue;
    }
}

方案二:使用自定义TokenCredential集成手动获取的Token

如果必须使用自己获取的Access Token,需要实现TokenCredential接口,将自定义Token注入SDK:

1. 实现自定义TokenCredential

public class CustomTokenCredential implements TokenCredential {
    private final String accessToken;

    public CustomTokenCredential(String accessToken) {
        this.accessToken = accessToken;
    }

    @Override
    public Mono<AccessToken> getToken(TokenRequestContext requestContext) {
        // 需自行处理Token过期,这里示例设置1小时过期
        return Mono.just(new AccessToken(accessToken, OffsetDateTime.now().plusHours(1)));
    }

    @Override
    public AccessToken getTokenSync(TokenRequestContext requestContext) {
        return new AccessToken(accessToken, OffsetDateTime.now().plusHours(1));
    }
}

2. 修改KeyVaultHelper使用自定义Credential

@Component
public class KeyVaultHelper {
    private static final Logger LOGGER = LoggerFactory.getLogger(KeyVaultHelper.class);
    private final String tenantId = "de8e2ba9-95c1-4fbb-b558-6bf8bb1d8081";
    private final RestTemplate restTemplate = new RestTemplate();

    public SecretClient getSecretClient() {
        String accessToken = getAccessToken();
        return new SecretClientBuilder()
                .vaultUrl("https://mba-key-vault-int.vault.azure.net/")
                .credential(new CustomTokenCredential(accessToken))
                .buildClient();
    }

    // 保留你的getAccessToken及相关方法
    public String getAccessToken() { 
        MultiValueMap<String,String> formData = prepareFormData();
        ResponseEntity<JsonNode> response = restTemplate.postForEntity(
                String.format("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId),
                httpEntity(formData),JsonNode.class); 
        JsonNode jsonNode=response.getBody(); 
        return jsonNode.get("access_token").asText(); 
    }

    // ... 其他prepareFormData、httpEntity方法不变
}

注意:该方式需要自行处理Token过期刷新,不如官方Credential实现便捷,仅在特殊场景下使用。


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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.06.18 23:00:03