本地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
相关产品推荐
相关产品推荐

