跳到主要内容
版本:3.10.x

客户端 mTLS 认证

双向 TLS(mTLS)在标准 TLS 的基础上要求客户端和服务器都在 TLS 握手期间出示证书。它可提供客户端身份的加密证明,适用于零信任架构、机器间通信,以及仅使用 API Key(API 密钥)或令牌认证不足以满足要求的环境。

在 API7 网关中,客户端 mTLS 配置在 SSL 对象上。该对象将监听域名(SNI)与服务器证书、私钥及用于验证客户端的 CA 证书绑定。启用 mTLS 后,网关会要求客户端在 TLS 握手完成前出示由已配置 CA 之一签名的证书。

信息

本文介绍 API 客户端网关之间的 mTLS。控制面和数据面之间的 mTLS,请参见控制面与数据面之间的双向 TLS;网关和上游服务器之间的 mTLS,请参见上游 mTLS

工作原理

  1. 客户端发起到网关的 TLS 连接,并在 ClientHello 的 SNI 扩展中包含目标域名。
  2. 网关将 SNI 匹配到已配置客户端 mTLS 的 SSL 对象。
  3. 网关出示服务器证书,并请求客户端证书。
  4. 客户端出示证书,网关使用已配置 CA 验证其证书链。
  5. 验证成功则 TLS 握手完成,请求被转发至上游;验证失败则在 TLS 层拒绝连接。

前置条件

准备以下 PEM 编码证书:

证书用途
服务器证书 + 私钥网关向客户端出示。
CA 证书网关用于验证客户端证书。
客户端证书 + 私钥客户端向网关出示,必须由上述 CA 签名。

如果没有用于测试的证书集,请使用以下命令生成:

# 生成 CA
openssl req -x509 -newkey rsa:2048 -keyout ca.key -out ca.crt -days 365 -nodes \
-subj "/CN=Test CA"

# 生成服务器证书
openssl req -newkey rsa:2048 -keyout server.key -out server.csr -nodes \
-subj "/CN=api.example.com"
openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out server.crt -days 365

# 生成客户端证书
openssl req -newkey rsa:2048 -keyout client.key -out client.csr -nodes \
-subj "/CN=test-client"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365

还需要一个用于接收流量的路由。以下示例使用 httpbin.org 服务和 /anything 路由。

配置客户端 mTLS

第 1 步:创建服务和路由

客户端 mTLS 基于 SNI 选择在 TLS 握手期间强制执行,早于任何路由决策。验证握手本身无需路由,但你需要路由才能从上游收到有意义的 HTTP 响应。因此请先创建最小服务和路由。

# 创建包含上游的服务
curl -k "https://localhost:7443/apisix/admin/services/httpbin?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "httpbin",
"upstream": {
"type": "roundrobin",
"scheme": "http",
"nodes": [
{ "host": "httpbin.org", "port": 80, "weight": 1 }
]
}
}'

# 在该服务下创建路由
curl -k "https://localhost:7443/apisix/admin/routes/get-anything?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d '{
"name": "get-anything",
"service_id": "httpbin",
"paths": ["/anything"],
"methods": ["GET"]
}'

{gateway_group_id} 替换为你的网关组 ID;快速入门创建的网关组请使用 default

第 2 步:创建启用客户端 mTLS 的 SSL 对象

创建 SSL 对象,将 SNI(api.example.com)、服务器证书和密钥,以及用于验证客户端证书的 CA 绑定。设置 client.ca 字段即可为该 SNI 启用客户端 mTLS。

SERVER_CRT=$(awk 1 ORS='\\n' server.crt)
SERVER_KEY=$(awk 1 ORS='\\n' server.key)
CA_CRT=$(awk 1 ORS='\\n' ca.crt)

curl -k "https://localhost:7443/apisix/admin/ssls/client-mtls-ssl?gateway_group_id={gateway_group_id}" -X PUT \
-H "X-API-KEY: ${API_KEY}" \
-H "Content-Type: application/json" \
-d "{
\"snis\": [\"api.example.com\"],
\"cert\": \"${SERVER_CRT}\",
\"key\": \"${SERVER_KEY}\",
\"client\": {
\"ca\": \"${CA_CRT}\"
}
}"

awk 1 ORS='\\n' 调用通过转义换行符,在 JSON 正文中保留多行 PEM 内容。

第 3 步:验证

发送包含有效客户端证书的请求。网关在端口 9443 监听 HTTPS 流量。

curl --cacert ca.crt --cert client.crt --key client.key \
--resolve api.example.com:9443:127.0.0.1 \
https://api.example.com:9443/anything

应收到来自 httpbin.org200 OK 响应和 JSON 正文。

不带客户端证书发送相同请求。任何 HTTP 请求发送前,TLS 握手应失败:

curl --cacert ca.crt \
--resolve api.example.com:9443:127.0.0.1 \
https://api.example.com:9443/anything

curl 会以 SSL 错误退出,网关错误日志会记录 SSL_do_handshake() failed (... peer did not return a certificate)

从密钥提供商引用证书

你无需将 PEM 内容直接嵌入 SSL 对象,可使用 $secret://... URI 格式引用 HashiCorp Vault、AWS Secrets Manager 或 Kubernetes Secrets 中存储的证书。可用密钥提供商及各自 URI 格式请参阅安全凭证管理

安全注意事项

  • Host 标头验证:启用 mTLS 后,网关会验证 HTTP Host 标头是否与 TLS 握手所用 SNI 匹配。如果二者不同,且两个域都启用了 mTLS 并使用不同的 CA 证书,系统会拒绝该请求,以防止证书混淆攻击。
  • TLS 会话恢复:网关会验证恢复的 TLS 会话是否与原始 SNI 一致,因此无法将会话票据复用于其他 SNI 以绕过 mTLS。
  • 证书轮换:请在到期前规划 CA 和客户端证书轮换。轮换 CA 时,将旧 CA 和新 CA 捆绑包拼接后一起配置到 SSL 对象的 client.ca 中,以便两种 CA 的客户端均可连接。

后续步骤