Java使用crt和pkcs8密钥建立HTTPS连接获取Azure AD令牌方法
Java原生HTTP API实现Azure AD客户端证书认证获取访问令牌
核心逻辑说明
整个实现仅依赖JDK原生API,无需导入证书到系统密钥库,也无需引入msal4j等第三方依赖,流程分为三个核心环节:加载本地证书与私钥、生成符合Azure AD要求的JWT客户端断言、调用令牌接口换取访问令牌。
前置准备
- 已在Azure AD应用注册页上传客户端公钥证书,获取到应用客户端ID、所属租户ID
- 本地持有
.crt格式的客户端公钥证书、无加密的PKCS8格式客户端私钥文件 - JDK版本为8及以上
完整实现代码
import java.io.FileInputStream; import java.net.URLEncoder; import java.net.URL; import java.nio.charset.StandardCharsets; import java.security.KeyFactory; import java.security.MessageDigest; import java.security.PrivateKey; import java.security.Signature; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.security.spec.PKCS8EncodedKeySpec; import java.util.Base64; import java.util.UUID; import javax.net.ssl.HttpsURLConnection; import org.json.JSONObject; // 若无需解析响应可删除,也可用原生字符串解析替代 public class AzureAdCertAuth { // 配置参数,按需替换 private static final String TENANT_ID = "<你的Azure AD租户ID>"; private static final String CLIENT_ID = "<你的应用客户端ID>"; private static final String CERT_PATH = "<本地crt证书文件路径>"; private static final String PRIVATE_KEY_PATH = "<本地PKCS8格式私钥文件路径>"; private static final String SCOPE = "https://graph.microsoft.com/.default"; private static final String TOKEN_ENDPOINT = "https://login.microsoftonline.com/" + TENANT_ID + "/oauth2/v2.0/token"; public static void main(String[] args) throws Exception { // 1. 加载本地证书和私钥 X509Certificate cert = loadCertificate(CERT_PATH); PrivateKey privateKey = loadPrivateKey(PRIVATE_KEY_PATH); // 2. 生成JWT客户端断言 String jwtAssertion = generateJwtAssertion(cert, privateKey); // 3. 调用Azure AD令牌接口获取访问令牌 String accessToken = getAccessToken(jwtAssertion); System.out.println("获取到的访问令牌:" + accessToken); } // 加载crt格式公钥证书 private static X509Certificate loadCertificate(String certPath) throws Exception { try (FileInputStream fis = new FileInputStream(certPath)) { CertificateFactory cf = CertificateFactory.getInstance("X.509"); return (X509Certificate) cf.generateCertificate(fis); } } // 加载PKCS8格式私钥 private static PrivateKey loadPrivateKey(String keyPath) throws Exception { String keyContent = new String(java.nio.file.Files.readAllBytes(java.nio.file.Paths.get(keyPath)), StandardCharsets.UTF_8) .replaceAll("-----BEGIN PRIVATE KEY-----", "") .replaceAll("-----END PRIVATE KEY-----", "") .replaceAll("\\s", ""); byte[] keyBytes = Base64.getDecoder().decode(keyContent); PKCS8EncodedKeySpec keySpec = new PKCS8EncodedKeySpec(keyBytes); KeyFactory kf = KeyFactory.getInstance("RSA"); return kf.generatePrivate(keySpec); } // Base64URL编码(符合JWT规范) private static String base64UrlEncode(byte[] content) { return Base64.getUrlEncoder().withoutPadding().encodeToString(content); } // 生成Azure AD要求的JWT断言 private static String generateJwtAssertion(X509Certificate cert, PrivateKey privateKey) throws Exception { // 计算证书SHA1指纹生成x5t参数 MessageDigest md = MessageDigest.getInstance("SHA-1"); byte[] certHash = md.digest(cert.getEncoded()); String x5t = base64UrlEncode(certHash); // JWT头 String header = "{\"alg\":\"RS256\",\"typ\":\"JWT\",\"x5t\":\"" + x5t + "\"}"; String encodedHeader = base64UrlEncode(header.getBytes(StandardCharsets.UTF_8)); // JWT载荷 long now = System.currentTimeMillis() / 1000; long exp = now + 300; // 断言有效期5分钟 String payload = String.format( "{\"iss\":\"%s\",\"sub\":\"%s\",\"aud\":\"%s\",\"exp\":%d,\"nbf\":%d,\"jti\":\"%s\"}", CLIENT_ID, CLIENT_ID, TOKEN_ENDPOINT, exp, now, UUID.randomUUID().toString() ); String encodedPayload = base64UrlEncode(payload.getBytes(StandardCharsets.UTF_8)); // 签名 String signInput = encodedHeader + "." + encodedPayload; Signature signature = Signature.getInstance("SHA256withRSA"); signature.initSign(privateKey); signature.update(signInput.getBytes(StandardCharsets.UTF_8)); String encodedSignature = base64UrlEncode(signature.sign()); return encodedHeader + "." + encodedPayload + "." + encodedSignature; } // 调用令牌接口获取访问令牌 private static String getAccessToken(String jwtAssertion) throws Exception { // 构造表单参数 String params = "grant_type=client_credentials" + "&client_id=" + URLEncoder.encode(CLIENT_ID, StandardCharsets.UTF_8.name()) + "&client_assertion_type=" + URLEncoder.encode("urn:ietf:params:oauth:client-assertion-type:jwt-bearer", StandardCharsets.UTF_8.name()) + "&client_assertion=" + URLEncoder.encode(jwtAssertion, StandardCharsets.UTF_8.name()) + "&scope=" + URLEncoder.encode(SCOPE, StandardCharsets.UTF_8.name()); // 发起POST请求 URL url = new URL(TOKEN_ENDPOINT); HttpsURLConnection conn = (HttpsURLConnection) url.openConnection(); conn.setRequestMethod("POST"); conn.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); conn.setDoOutput(true); conn.getOutputStream().write(params.getBytes(StandardCharsets.UTF_8)); // 解析响应 if (conn.getResponseCode() == 200) { String response = new String(conn.getInputStream().readAllBytes(), StandardCharsets.UTF_8); JSONObject json = new JSONObject(response); return json.getString("access_token"); } else { String error = new String(conn.getErrorStream().readAllBytes(), StandardCharsets.UTF_8); throw new RuntimeException("获取令牌失败:" + error); } } }
注意事项
- 私钥如果不是PKCS8格式,可使用openssl命令转换:
openssl pkcs8 -topk8 -inform PEM -in 原始私钥路径 -outform PEM -nocrypt -out 输出PKCS8私钥路径 - JWT头必须携带
x5t参数,否则Azure AD会返回签名验证失败错误 - scope参数可根据实际需要访问的资源替换,默认值对应Microsoft Graph API的应用权限
- 默认JDK信任库已包含Azure公共根证书,若需对接私有环境可自定义SSLContext配置信任规则
内容的提问来源于stack exchange,提问作者user14734781
相关产品推荐
相关产品推荐

