双向认证(mTLS,mutual TLS)的配置核心只有三步:自建或使用受信任 CA 签发客户端证书,在服务端开启「校验客户端证书」,再把客户端证书和私钥分发给调用方。它只适合调用方身份可以确定、数量可控的场景,例如 B2B API 对接、内部微服务、IoT 设备接入;不适合面向普通用户的公开网站。
双向认证和单向 SSL 有什么区别?
结论:单向 SSL 只验证服务器身份,双向认证同时验证服务器和客户端身份,握手阶段服务端会要求客户端出示 CA 签发的证书。
单向 HTTPS 里,浏览器校验证书链就完成握手;双向认证中,服务端在 TLS 握手的 CertificateRequest 阶段索要客户端证书,拿不到有效证书就直接中断连接(Nginx 返回 400,Apache 返回 403),业务代码根本不会被调用。因此,双向认证属于传输层准入控制,在性能开销上只增加一次证书校验,比在应用层做签名校验更彻底,也更适合拦截未授权的调用。
双向认证的证书怎么生成?
结论:只需要一条完整的信任链——根 CA、服务端证书、客户端证书,客户端证书必须带 extendedKeyUsage=clientAuth 扩展。
具体步骤如下(使用 OpenSSL 1.1.1 及以上版本):
- 生成根 CA(有效期 10 年):
openssl genrsa -out ca.key 4096
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 -out ca.crt \
-subj "/C=CN/O=GJ/CN=GJ Internal CA"
- 为每个调用方签发客户端证书(也可以使用
openssl ca 批量签发):
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr -subj "/C=CN/O=GJ/CN=client-001"
openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key -CAcreateserial \
-out client.crt -days 365 -sha256 -extfile <(printf "extendedKeyUsage=clientAuth")
- 打包成客户端可导入的格式:
openssl pkcs12 -export -out client.p12 -inkey client.key -in client.crt -certfile ca.crt
注意:根 CA 私钥必须离线保存;客户端证书建议按「一设备一证书」签发,并将 CN 或 SAN 写成设备编号或应用 ID,方便后续按身份做灰度或封禁。
Nginx 怎么配置双向认证?
结论:在 server 块加三行关键指令即可,ssl_verify_client on 是总开关。
server {
listen 443 ssl http2;
server_name api.example.com;
ssl_certificate /etc/nginx/ssl/server.crt;
ssl_certificate_key /etc/nginx/ssl/server.key;
ssl_client_certificate /etc/nginx/ssl/ca.crt; # 信任的 CA
ssl_verify_client on; # 强制校验
ssl_verify_depth 2;
ssl_protocols TLSv1.2 TLSv1.3;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Client-Verify $ssl_client_verify;
proxy_set_header X-Client-DN $ssl_client_s_dn;
proxy_set_header X-Client-Serial $ssl_client_serial;
}
}
Nginx 1.19.0 及以上版本支持 ssl_crl 指令加载吊销列表,这是开源版吊销客户端证书的唯一方式(客户端证书的 OCSP 校验在开源版不可用)。如果只想做软校验,可以改成 ssl_verify_client optional_no_ca,由后端读取 $ssl_client_verify 自行判断。测试命令:curl --cert client.crt --key client.key https://api.example.com/,不带证书时应直接失败。
Java 和 Apache 环境怎么配?
结论:Tomcat 或 Spring Boot 通过 trust-store 校验客户端证书,Apache 用 SSLVerifyClient require。
Spring Boot 3.x 的 application.yml:
server:
ssl:
enabled: true
key-store: classpath:server.p12
key-store-password: changeit
client-auth: need # need = 强制双向认证
trust-store: classpath:truststore.p12
trust-store-password: changeit
client-auth 可选 none / want / need,生产强制场景用 need。业务层取值:X509Certificate[] certs = (X509Certificate[]) request.getAttribute("jakarta.servlet.request.X509Certificate");(Spring Boot 3 / Jakarta EE 10 的键名;2.x 为 javax.servlet.request.X509Certificate)。
Apache httpd:
SSLVerifyClient require
SSLVerifyDepth 2
SSLCACertificateFile /etc/httpd/ssl/ca.crt
SSLUserName SSL_CLIENT_S_DN_CN
双向认证适合哪些业务场景?
结论:适合「调用方是组织、设备或内部系统,且数量可枚举」的场景;不适合面向大众的网站和 C 端小程序。
明确适用的五类:
- B2B / 开放平台 API 对接:每个合作方一张客户端证书,出问题可按证书序列号精确封禁。
- 内部微服务与网关入口:服务间 mTLS 做零信任基础层,Istio、Linkerd 默认即开启。
- IoT 设备接入:设备出厂预置证书,比账号密码更难复制,配合一设备一证书做设备白名单。
- 金融、医疗、政务合规系统:等保三级、密评场景常明确要求双向身份鉴别。
- 远程运维入口:运维跳板机、内网管理后台,把「能不能访问」提前到 TLS 层拦截。
明确不适用的:
- 公开内容站点:浏览器会弹出证书选择框,无证书用户直接看不到页面,搜索引擎爬虫也无法抓取。
- 微信小程序、部分移动 App 的 HTTP 客户端:不支持配置客户端证书,硬上会直接导致接入失败。
- 大多数 CDN:CDN 在边缘终止 TLS,客户端证书校验需要透传,多数公共 CDN 做不到,必须使用支持 mTLS 的私有节点或专线方案。
上生产前必须注意什么?
结论:双向认证的运维成本主要在证书生命周期,而不是配置本身。
三个要点:有效期与续期——客户端证书建议 1 年以内,到期前 30 天自动提醒,避免批量失联;吊销通道——离职、设备下线要立即 openssl ca -revoke 并更新 CRL 或私有 OCSP,否则证书仍可使用;私钥保护——客户端私钥泄露等于身份被盗,p12 文件应加密传输、单独下发。另外,服务端与客户端机器时间不同步会导致证书校验失败,务必部署 NTP。
回到开头:双向认证的配置量不大,Nginx 三行指令加一套 CA 就能跑通,难的是把证书的签发、分发、续期、吊销做成流程。只要你的调用方是「能数得清」的组织或设备,它就比任何应用层密钥都好用;一旦面对不可控的公众用户,就该老老实实回到单向 HTTPS + 应用层令牌的方案。