Nginx mTLS 双向认证实战:客户端证书验证配置

适用场景

Nginx mTLS(双向 TLS 认证)适用于对调用方身份有强认证要求的场景:

  • 内部管理后台、运维平台只允许持有客户端证书的办公终端访问;
  • 开放 API 只向少数可信合作方提供服务,要求”密码之外再加一层证书”;
  • 服务间通信要求双向身份验证,防止接口地址泄露后被任意调用;
  • 零信任架构下,用证书替代来源 IP 白名单,避免动态 IP 带来的维护成本。

前置条件

  • 服务器已安装 Nginx 并完成 HTTPS 基础配置(单向 TLS 正常工作);
  • 服务器安装 OpenSSL(CentOS/RHEL:yum install openssl;Debian/Ubuntu:apt install openssl);
  • 客户端测试机安装 curl(需 7.53+ 以支持 –cert 参数的 PEM 直传);
  • 如走公网分发给浏览器用户,需要将 CA 证书安装到客户端信任区。

原理说明

普通 HTTPS 只验证服务器身份;mTLS 在 TLS 握手过程中增加一步:服务器通过 CertificateRequest 消息向客户端索要证书,客户端必须出示由服务器信任的 CA 签发的证书,双方各自验证对端证书链后才建立连接。Nginx 通过 ssl_verify_client 指令开启该机制,配合 ssl_client_certificate 指定信任的 CA。即使攻击者拿到接口地址,没有客户端证书与私钥也无法完成握手,相当于把”身份凭据”从可猜测的口令升级为不可伪造的非对称密钥对。

操作步骤

1. 创建自签 CA

mkdir -p /etc/nginx/mtls && cd /etc/nginx/mtls

# 生成 CA 私钥(4096 位)
openssl genrsa -out ca.key 4096

# 生成 CA 证书,有效期 10 年
openssl req -x509 -new -nodes -key ca.key -sha256 -days 3650 \
  -out ca.crt -subj "/CN=Wafai Internal CA"

2. 签发服务器证书

# 服务器私钥与 CSR
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
  -subj "/CN=api.wafai.cn"

# 带 SAN 扩展签发(浏览器校验依赖 SAN,不再认 CN)
cat > server.ext <<'EOF'
authorityKeyIdentifier=keyid,issuer
basicConstraints=CA:FALSE
keyUsage = digitalSignature, keyEncipherment
extendedKeyUsage = serverAuth
subjectAltName = @alt_names
[alt_names]
DNS.1 = api.wafai.cn
EOF

openssl x509 -req -in server.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out server.crt -days 825 -sha256 -extfile server.ext

3. 签发客户端证书

openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
  -subj "/CN=wafai-client"

cat > client.ext <<'EOF'
basicConstraints=CA:FALSE
keyUsage = digitalSignature
extendedKeyUsage = clientAuth
EOF

openssl x509 -req -in client.csr -CA ca.crt -CAkey ca.key \
  -CAcreateserial -out client.crt -days 365 -sha256 -extfile client.ext

# 打包成 PKCS#12 供浏览器导入
openssl pkcs12 -export -out client.p12 -inkey client.key \
  -in client.crt -certfile ca.crt -passout pass:YourP12Pass

客户端证书的 extendedKeyUsage 必须为 clientAuth,否则 Nginx 校验会失败。

4. 配置 Nginx

server {
    listen 443 ssl;
    server_name api.wafai.cn;

    ssl_certificate     /etc/nginx/mtls/server.crt;
    ssl_certificate_key /etc/nginx/mtls/server.key;

    # 开启双向认证:on 表示强制要求客户端证书
    ssl_client_certificate /etc/nginx/mtls/ca.crt;
    ssl_verify_client on;
    ssl_verify_depth 2;
    ssl_protocols TLSv1.2 TLSv1.3;

    # 将客户端证书信息透传给后端应用(可选)
    proxy_set_header X-Client-DN $ssl_client_s_dn;

    location / {
        proxy_pass http://127.0.0.1:8080;
    }
}

ssl_verify_client on 为强制模式;若需兼容无证书的只读访客,可设为 optional,再在应用层结合 $ssl_client_verify 判断。改完执行 nginx -t && systemctl reload nginx。

配置验证

# 携带客户端证书访问:应返回 200 与后端内容
curl --cacert ca.crt --cert client.crt --key client.key \
  https://api.wafai.cn/

# 不带证书访问:应返回 400
curl --cacert ca.crt https://api.wafai.cn/

无证书请求会得到 400 No required SSL certificate was sent,这是预期的拒绝结果。同时确认 Nginx error.log 无 client SSL certificate verify error 记录。

常见问题

FAQ 1:客户端证书校验报 400 "No required SSL certificate was sent"?

依次排查:客户端是否确实携带了证书与私钥;证书 extendedKeyUsage 是否为 clientAuth;中间是否有 CDN、云 WAF 等代理剥离了 TLS 层——mTLS 要求 Nginx 直接终止 TLS,四层透传(如 CDN 的 TCP 模式)才能保留客户端证书。

FAQ 2:证书需要吊销某个客户端怎么办?

自建 CA 可通过 CRL 吊销:用 openssl ca -gencrl 生成 crl.pem,在 Nginx 中配置 ssl_crl /etc/nginx/mtls/crl.pem 后 reload。客户端证书有效期建议控制在 1 年内,并建立定期轮换流程。

总结

mTLS 通过在 TLS 握手中加入客户端证书验证,把服务器单向认证升级为双向强认证,是内部系统与开放 API 最经济有效的接入控制手段。部署要点:自建 CA 管理证书生命周期、服务器证书必须包含 SAN、客户端证书 extendedKeyUsage 为 clientAuth、Nginx 直接终止 TLS。上线后可通过 ssl_verify_client optional 与 $ssl_client_verify 变量实现灰度过渡,避免一刀切切换造成业务中断。