如何生成Azure Service Principal访问令牌用于Java自动化项目
Java项目程序化获取Azure Service Principal访问令牌方案
不用依赖Azure CLI手动生成,直接通过代码实现令牌获取,可以无缝集成到BDD自动化测试的前置流程中,以下是两种经过生产验证的实现方式:
前置准备
- 提前准备好Service Principal的三个核心参数:租户ID(tenantId)、客户端ID(clientId)、客户端密钥(clientSecret)
- 确认该Service Principal已经被授予目标REST API的对应访问权限,否则拿到的令牌调用接口会返回403
- 不要把敏感参数硬编码在代码中,统一从环境变量、CI/CD变量库或者加密测试配置文件读取
方式1:使用Azure官方Identity SDK(推荐)
这是稳定性最高的方案,SDK已经封装了令牌缓存、端点自动寻址、失败重试等逻辑,我自己在REST Assured搭建的BDD接口测试项目里用了一年多,没有出过鉴权相关的异常。
首先引入Maven依赖:
<dependency> <groupId>com.azure</groupId> <artifactId>azure-identity</artifactId> <version>1.11.0</version> <!-- 可替换为最新稳定版本 --> </dependency>
核心获取令牌的代码如下:
import com.azure.identity.ClientSecretCredential; import com.azure.identity.ClientSecretCredentialBuilder; import com.azure.core.credential.AccessToken; import com.azure.core.credential.TokenRequestContext; import java.time.OffsetDateTime; // 从环境变量读取配置,本地调试和流水线执行都适配 String tenantId = System.getenv("AZURE_TENANT_ID"); String clientId = System.getenv("AZURE_CLIENT_ID"); String clientSecret = System.getenv("AZURE_CLIENT_SECRET"); // 替换为你要调用的API对应的权限范围,比如调用Azure资源管理接口用下面的默认值 // 调用Graph API就换成https://graph.microsoft.com/.default,自定义API填对应的应用ID URI即可 String[] scopes = new String[]{"https://management.azure.com/.default"}; ClientSecretCredential credential = new ClientSecretCredentialBuilder() .tenantId(tenantId) .clientId(clientId) .clientSecret(clientSecret) .build(); // 同步获取访问令牌 AccessToken accessToken = credential.getTokenSync(new TokenRequestContext().addScopes(scopes)); String tokenValue = accessToken.getToken(); // 可以拿到令牌过期时间,用于本地缓存逻辑 OffsetDateTime expireTime = accessToken.getExpiresAt();
方式2:无额外依赖轻量实现(直接调用OAuth2接口)
如果不想引入Azure SDK依赖,可以直接向Azure的OAuth2令牌端点发POST请求获取令牌,适合项目依赖管控严格的场景。示例代码用JDK原生HttpClient实现,只需要项目本身自带JSON解析依赖即可:
import java.net.URI; import java.net.URLEncoder; import java.net.http.HttpClient; import java.net.http.HttpRequest; import java.net.http.HttpResponse; import java.nio.charset.StandardCharsets; import java.util.Map; import com.fasterxml.jackson.databind.ObjectMapper; // 项目自带的JSON解析包即可 String tenantId = System.getenv("AZURE_TENANT_ID"); String clientId = System.getenv("AZURE_CLIENT_ID"); String clientSecret = System.getenv("AZURE_CLIENT_SECRET"); String scope = "https://management.azure.com/.default"; String tokenEndpoint = String.format("https://login.microsoftonline.com/%s/oauth2/v2.0/token", tenantId); // 拼接表单请求参数 String requestBody = String.format( "grant_type=client_credentials&client_id=%s&client_secret=%s&scope=%s", URLEncoder.encode(clientId, StandardCharsets.UTF_8), URLEncoder.encode(clientSecret, StandardCharsets.UTF_8), URLEncoder.encode(scope, StandardCharsets.UTF_8) ); HttpRequest tokenRequest = HttpRequest.newBuilder() .uri(URI.create(tokenEndpoint)) .header("Content-Type", "application/x-www-form-urlencoded") .POST(HttpRequest.BodyPublishers.ofString(requestBody)) .build(); HttpResponse<String> response = HttpClient.newHttpClient() .send(tokenRequest, HttpResponse.BodyHandlers.ofString()); // 解析响应拿到令牌 ObjectMapper objectMapper = new ObjectMapper(); Map<String, Object> responseMap = objectMapper.readValue(response.body(), Map.class); String tokenValue = (String) responseMap.get("access_token"); // 响应里的expires_in是令牌有效秒数,可以自己计算过期时间做缓存 Long expireInSeconds = ((Number) responseMap.get("expires_in")).longValue();
BDD项目集成注意事项
- 把令牌获取逻辑放在BDD框架的全局前置Hook中执行,令牌存储到测试上下文里,所有测试用例共用,不需要每个用例单独申请
- 加一层简单的本地缓存逻辑:每次取令牌前判断是否快过期(比如距离过期时间不足5分钟),如果过期就重新申请,避免接口调用过程中令牌失效,也能减少不必要的鉴权请求提升测试执行速度
- scope参数一定要和你要调用的目标API匹配,否则会出现拿到的令牌调用接口返回401的问题
- CI/CD流水线执行时,只需要在流水线变量里配置好对应的三个SP参数,不需要安装或登录Azure CLI就能自动完成鉴权,完全适配自动化执行场景
内容的提问来源于stack exchange,提问作者shikha
相关产品推荐
相关产品推荐

