OpenAI X.509 身份联合配置

2026-11-09 15:44 更新

X.509 身份联合将 TLS 客户端证书身份换为短期 OpenAI 访问令牌。之后 API 请求同时发送令牌和被接受的客户端证书,替换的是 API 密钥,而非客户端证书。

仅支持 OpenAI API,Codex 不支持此路径,须用 OIDC 或 SPIFFE JWT-SVID。交换字段见交换参考,权限、证书、激活、主机与轮换见 mTLS 指南。

工作原理

  1. 在现有 mTLS 设置上传并激活可信根证书。
  2. X.509 提供商从已验证证书派生 openai.*,必须有一个非空 openai.subject。
  3. 映射授权该身份使用某项目的一个服务账户。
  4. 工作负载向 mtls.auth.openai.com 的 X.509 交换端点呈交 TLS 证书,取得短期 bearer;证书来自 TLS 连接,请求体没有 subject_token。
  5. 向 mtls.api.openai.com 的 API 路径同时发送 bearer 和证书。

API 请求独立检查令牌与证书,单凭证书不能调用 API。

开始前

需要管理组织 mTLS 与身份提供商的权限、目标项目和服务账户、客户端证书/私钥及必要中间证书、已在组织或项目激活的可信根。私钥不入版本库,仅工作负载可访问;不要记录私钥、证书或返回令牌。

配置 mTLS 证书信任

复用组织现有 mTLS,不单独上传或建立信任库。先读 mTLS 指南,再到组织 mTLS 设置上传 PEM 信任证书,在组织或每个目标项目激活。

客户端经中间证书链到根时,配置稳定锚,TLS 握手依次提交叶证书及当前中间证书。OpenAI 使用请求提供的链,不从证书 URL 补取缺失部分。

配置 X.509 提供商

  1. 在组织工作负载身份设置选择 Create identity provider。
  2. Provider type 选 X.509,输入名称和可选描述;不使用 OIDC issuer、audience、discovery 或 JWKS,创建后不能改类型。
  3. Advanced 可加 Attribute conditions CEL,在映射前拒绝不合格证书。
  4. Attribute transformations 为必需 openai.subject 输入非空表达式。选 X.509 后界面自动出现 subject 行,显示并应用 openai. 前缀,选择稳定识别工作负载的证书事实。
  5. 按需增加唯一的 openai.* 派生项并创建。

示例用 common name 作为标准主体、组织单元作为附加属性:

来源示例 1(json):

中文说明:来源配置或结果示例,按所在小节的字段、作用域与权限要求解释;JSON保持合法格式,CEL/声明匹配文本不是可独立运行的应用。

[
  {
    "attribute": "openai.subject",
    "expression": "assertion.subject.common_name"
  },
  {
    "attribute": "openai.environment",
    "expression": "assertion.subject.organizational_unit"
  }
]

证书事实在 assertion.subject、assertion.subject_alt_names;用于映射的转换须产生标量,附加名称须唯一。仅允许生产证书的条件示例:

来源示例 2(text):

中文说明:来源配置或结果示例,按所在小节的字段、作用域与权限要求解释;JSON保持合法格式,CEL/声明匹配文本不是可独立运行的应用。

assertion.subject.organizational_unit == "Production"

创建服务账户映射

在提供商详情选 Create mapping;选择目标项目和账户,仅授予必要 API 权限;Key/Value 优先要求精确 openai.subject。X.509 映射可以无断言(空对象 {}),或断言键都以 openai. 开头,然后创建。

键 值
openai.subject payments-service-prod

它只匹配派生属性,不匹配 JWT 的 sub、iss、aud。提供商列表显示 provider ID,映射详情显示 service account ID,记录两者用于交换。

在 SDK 中使用 X.509 身份

设置证书链、私钥、提供商和服务账户环境变量:

来源示例 3(bash):

## [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
export OPENAI_MTLS_CERT_CHAIN="/path/to/client-chain.pem"
export OPENAI_MTLS_KEY="/path/to/client-key.pem"
export OPENAI_IDENTITY_PROVIDER_ID="idp_example"
export OPENAI_SERVICE_ACCOUNT_ID="svc_acct_example"

链文件先叶证书后中间证书,HTTP 请求体不包含证书或 subject_token。SDK 在交换与 API 调用中呈交证书,使用 mTLS 主机并自动续期:

来源示例 4(javascript):

// [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
import { readFile } from "node:fs/promises";

import OpenAI from "openai";
import { workloadIdentity } from "openai/auth/x509-transport";

const certificatePath = process.env.OPENAI_MTLS_CERT_CHAIN;
const privateKeyPath = process.env.OPENAI_MTLS_KEY;
const identityProviderId = process.env.OPENAI_IDENTITY_PROVIDER_ID;
const serviceAccountId = process.env.OPENAI_SERVICE_ACCOUNT_ID;

if (
  !certificatePath ||
  !privateKeyPath ||
  !identityProviderId ||
  !serviceAccountId
) {
  throw new Error(
    "Set OPENAI_MTLS_CERT_CHAIN, OPENAI_MTLS_KEY, OPENAI_IDENTITY_PROVIDER_ID, and OPENAI_SERVICE_ACCOUNT_ID"
  );
}

const credential = workloadIdentity.fromX509({
  certificateChain: await readFile(certificatePath, "utf8"),
  privateKey: await readFile(privateKeyPath, "utf8"),
  identityProviderId,
  serviceAccountId,
});

try {
  const client = new OpenAI({ credential });
  const response = await client.responses.create({
    model: "gpt-5.6-terra",
    input: "Say hello from X.509 workload identity federation.",
  });

  console.log(response.output_text);
} finally {
  await credential.close();
}

来源示例 5(python):

## [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
import os
import ssl

from openai import DefaultHttpx2Client, OpenAI
from openai.auth import x509_workload_identity

tls_context = ssl.create_default_context()
tls_context.load_cert_chain(
    certfile=os.environ["OPENAI_MTLS_CERT_CHAIN"],
    keyfile=os.environ["OPENAI_MTLS_KEY"],
)

with OpenAI(
    base_url="https://mtls.api.openai.com/v1",
    workload_identity=x509_workload_identity(
        identity_provider_id=os.environ["OPENAI_IDENTITY_PROVIDER_ID"],
        service_account_id=os.environ["OPENAI_SERVICE_ACCOUNT_ID"],
    ),
    http_client=DefaultHttpx2Client(verify=tls_context, follow_redirects=False),
) as client:
    response = client.responses.create(
        model="gpt-5.6-terra",
        input="Say hello from X.509 workload identity federation.",
    )

    print(response.output_text)

来源示例 6(go):

// [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
package main

import (
    "context"
    "crypto/tls"
    "fmt"
    "log"
    "net/http"
    "os"

    "github.com/openai/openai-go/v3"
    "github.com/openai/openai-go/v3/auth"
    "github.com/openai/openai-go/v3/option"
    "github.com/openai/openai-go/v3/responses"
)

func main() {
    certificate, err := tls.LoadX509KeyPair(
        os.Getenv("OPENAI_MTLS_CERT_CHAIN"),
        os.Getenv("OPENAI_MTLS_KEY"),
    )
    if err != nil {
        log.Fatal(err)
    }

    transport, err := auth.NewX509Transport(&http.Transport{
        TLSClientConfig: &tls.Config{
            Certificates: []tls.Certificate{certificate},
            MinVersion:   tls.VersionTLS12,
        },
    })
    if err != nil {
        log.Fatal(err)
    }
    defer transport.Close()

    client := openai.NewClient(
        option.WithX509WorkloadIdentity(auth.X509WorkloadIdentity{
            IdentityProviderID: os.Getenv("OPENAI_IDENTITY_PROVIDER_ID"),
            ServiceAccountID:   os.Getenv("OPENAI_SERVICE_ACCOUNT_ID"),
            Transport:          transport,
        }),
    )

    response, err := client.Responses.New(context.Background(), responses.ResponseNewParams{
        Model: "gpt-5.6-terra",
        Input: responses.ResponseNewParamsInputUnion{
            OfString: openai.String("Say hello from X.509 workload identity federation."),
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    fmt.Println(response.OutputText())
}

来源示例 7(java):

// [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
import com.openai.client.OpenAIClient;
import com.openai.client.okhttp.OpenAIOkHttpClient;
import com.openai.client.okhttp.X509Transport;
import com.openai.client.okhttp.X509WorkloadIdentity;
import com.openai.models.responses.ResponseCreateParams;
import java.io.InputStream;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.security.KeyStore;
import java.time.Duration;
import java.util.Arrays;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.net.ssl.X509ExtendedKeyManager;
import javax.net.ssl.X509TrustManager;

char[] password = System.getenv("OPENAI_X509_KEYSTORE_PASSWORD").toCharArray();
try {
  KeyStore keyStore = KeyStore.getInstance("PKCS12");
  try (InputStream input =
      Files.newInputStream(Paths.get(System.getenv("OPENAI_X509_KEYSTORE_PATH")))) {
    keyStore.load(input, password);
  }

  KeyManagerFactory keyManagers =
      KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
  keyManagers.init(keyStore, password);
  X509ExtendedKeyManager keyManager =
      Arrays.stream(keyManagers.getKeyManagers())
          .filter(X509ExtendedKeyManager.class::isInstance)
          .map(X509ExtendedKeyManager.class::cast)
          .findFirst()
          .orElseThrow(() -> new IllegalStateException("No X.509 key manager available"));

  TrustManagerFactory trustManagers =
      TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
  trustManagers.init((KeyStore) null);
  X509TrustManager trustManager =
      Arrays.stream(trustManagers.getTrustManagers())
          .filter(X509TrustManager.class::isInstance)
          .map(X509TrustManager.class::cast)
          .findFirst()
          .orElseThrow(() -> new IllegalStateException("No X.509 trust manager available"));

  X509Transport transport =
      X509Transport.builder()
          .keyManager(keyManager)
          .certificateAlias(System.getenv("OPENAI_X509_CERTIFICATE_ALIAS"))
          .trustManager(trustManager)
          .build();
  X509WorkloadIdentity identity =
      X509WorkloadIdentity.builder()
          .identityProviderId(System.getenv("OPENAI_IDENTITY_PROVIDER_ID"))
          .serviceAccountId(System.getenv("OPENAI_SERVICE_ACCOUNT_ID"))
          .transport(transport)
          .refreshBuffer(Duration.ofMinutes(10))
          .build();

  OpenAIClient client = OpenAIOkHttpClient.builder().x509WorkloadIdentity(identity).build();
  try {
    ResponseCreateParams params =
        ResponseCreateParams.builder()
            .model("gpt-5.6-terra")
            .input("Say hello from X.509 workload identity federation.")
            .build();

    client.responses().create(params).output().stream()
        .flatMap(item -> item.message().stream())
        .flatMap(message -> message.content().stream())
        .flatMap(content -> content.outputText().stream())
        .forEach(outputText -> System.out.println(outputText.text()));
  } finally {
    client.close();
  }
} finally {
  Arrays.fill(password, '\0');
}

来源示例 8(ruby):

## [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
require "openai"
require "openssl"

certificate_chain = OpenSSL::X509::Certificate.load(
  File.binread(ENV.fetch("OPENAI_MTLS_CERT_CHAIN"))
)
certificate, *intermediates = certificate_chain
private_key = OpenSSL::PKey.read(File.binread(ENV.fetch("OPENAI_MTLS_KEY")))

http_client = OpenAI::NetHTTPClient.new do |connection|
  connection.cert = certificate
  connection.extra_chain_cert = intermediates
  connection.key = private_key
end

workload_identity = OpenAI::Auth::X509WorkloadIdentity.new(
  identity_provider_id: ENV.fetch("OPENAI_IDENTITY_PROVIDER_ID"),
  service_account_id: ENV.fetch("OPENAI_SERVICE_ACCOUNT_ID"),
  http_client: http_client
)

begin
  client = OpenAI::Client.new(api_key: nil, workload_identity: workload_identity)
  response = client.responses.create(
    model: "gpt-5.6-terra",
    input: "Say hello from X.509 workload identity federation."
  )

  puts(response.output_text)
ensure
  http_client.close
end

此配置要求 JavaScript 7.8.0+ 且安装 undici peer dependency,Python 3.6.0+,Go 3.54.0+,Java 4.55.0+,Ruby 0.83.0+。

Java 示例从 PKCS12 keystore 构建 X509ExtendedKeyManager,平台默认 trust store 构建 X509TrustManager。设置 OPENAI_X509_KEYSTORE_PATH、OPENAI_X509_KEYSTORE_PASSWORD、OPENAI_X509_CERTIFICATE_ALIAS,也可向 SDK 提供 PEM 或硬件支持的 manager。

手动交换证书

为检查或自行实现协议,向精确的 X.509 端点呈交证书:

来源示例 9(bash):

## [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
curl --cert "$OPENAI_MTLS_CERT_CHAIN" \
  --key "$OPENAI_MTLS_KEY" \
  --request POST "https://mtls.auth.openai.com/oauth/token" \
  --header "Content-Type: application/json" \
  --data @- <<JSON
{
  "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  "subject_token_type": "urn:openai:params:oauth:token-type:x509",
  "identity_provider_id": "${OPENAI_IDENTITY_PROVIDER_ID}",
  "service_account_id": "${OPENAI_SERVICE_ACCOUNT_ID}"
}
JSON

成功返回普通短期 bearer:

来源示例 10(json):

中文说明:来源配置或结果示例,按所在小节的字段、作用域与权限要求解释;JSON保持合法格式,CEL/声明匹配文本不是可独立运行的应用。

{
  "access_token": "eyJ...",
  "issued_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "token_type": "Bearer",
  "expires_in": 3600,
  "expires_at": 1789045200,
  "scope": "api.model.read api.model.request"
}

仅匹配映射设有 Permissions 时返回 scope。到期值只是示例,证书更早到期时实际令牌会更短。单位见响应字段。将 access_token 安全加载到应用凭证存储或 OPENAI_WIF_ACCESS_TOKEN 等环境变量,不打印、记录或提交版本库。

手动调用 API

OPENAI_MODEL 设为来源当前默认 gpt-6-astra 或目标项目可用的其他模型,携带 bearer 与证书请求 mTLS API:

来源示例 11(bash):

## [中文] X.509交换和后续API均携带客户端证书;Bearer替代API密钥,不替代证书,不打印返回的短期令牌。
curl --request POST \
  --cert "$OPENAI_MTLS_CERT_CHAIN" \
  --key "$OPENAI_MTLS_KEY" \
  --header "Authorization: Bearer $OPENAI_WIF_ACCESS_TOKEN" \
  --header "Content-Type: application/json" \
  --data "{\"model\":\"$OPENAI_MODEL\",\"input\":\"Say hello in one sentence.\"}" \
  "https://mtls.api.openai.com/v1/responses"

Bearer 替代 API 密钥,证书继续必需。令牌没有密码学绑定到证书。复用交换证书最直接,但也可使用另一张独立满足当前 API mTLS 政策的证书。

令牌寿命与续期

最长一小时,且不超过已验证证书的到期时间。不返回刷新令牌,须再次交换。手动管理时保留 expires_at,提前安排交换并留出时钟、网络余量,见续期指南。轮换中间证书无须改根,后续请求提交新完整链。

交换故障排查

返回通用 OAuth 错误,不暴露证书、根、提供商或映射详情:

结果 常见原因
HTTP 403 未使用 mtls.auth.openai.com 上精确 POST /oauth/token 的方法或路径
invalid_subject_token 客户端证书缺失/无效、链不达活动根、证书不在有效期或mTLS准入规则拒绝
invalid_grant 提供商/映射无效或停用、Attribute conditions拒绝、无活动适用根或无匹配映射
服务器错误 暂时服务器错误,按暂时故障策略重试

X.509 交换不会回退到 OIDC 或普通 OAuth 流程。

限制

  • 无独立证书信任库。
  • Bearer 非证书绑定,不使用 DPoP 或 cnf。
  • 不是仅证书 API 授权,后续仍需 bearer 和证书。
  • 不从 AIA 补取中间证书,握手须提交完整链。
  • 不做 CRL/OCSP 检查,应围绕 mTLS 根、提供商、映射控制及令牌短寿命设计证书事件响应。
  • 不增加 SPIFFE X.509-SVID 支持,SPIFFE 路径继续使用 JWT-SVID。
以上内容是否对您有帮助:
在线笔记
App下载
App下载

扫描二维码

下载编程狮App

公众号
微信公众号

编程狮公众号