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

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.10.16 16:45:02