在 Nginx 中配置客户端与服务端 SSL 双向认证,除开上一小节的指令,也涉及到以下几个指令:
指令参数
-
ssl_client_certificate 指令用于指定具有PEM格式的受信任CA证书签发的客户端证书,若启用了ssl_stapling,则该文件用于验证客户端证书和OCSP响应。
Syntax: ssl_client_certificate file;
Default: —
Context: http, server
-
ssl_trusted_certificate 指令用于指定具有PEM格式的受信任CA证书的文件,该文件用于验证客户端证书,与ssl_client_certificate 设置的证书不同,这些证书的列表不会发送给客户端。
Syntax: ssl_trusted_certificate file;
Default: —
Context: http, server
-
ssl_verify_client 指令用于指定是否启用客户端证书验证,可选值为 off、on 或 optional, 验证结果存储在$ssl_client_verify 变量中。
Syntax: ssl_verify_client on | off | optional | optional_no_ca;
Default: ssl_verify_client off;
Context: http, server
# 参数说明
# on 启用客户端证书验证
# off 禁用客户端证书验证
# optional 请求客户端证书并验证证书是否存在
# optional_no_ca 请求客户端证书,但不要求它由受信任的CA证书签名
-
ssl_verify_depth 指令用于设置客户端证书链中的验证深度,默认值为1。
Syntax: ssl_verify_depth number;
Default: ssl_verify_depth 1;
Context: http, server
-
ssl_ocsp 指令用于启用客户端证书链的OCSP验证,该功能允许服务器在握手期间验证客户端证书的有效性,而不是等到建立连接后才进行。
Syntax: ssl_ocsp on | off | leaf;
Default: ssl_ocsp off;
Context: http, server
# 参数说明
# on 启用客户端证书链的OCSP验证
# off 禁用客户端证书链的OCSP验证
# leaf 只验证客户端证书的签名,不验证整个链
# 示例:
# 若要解析OCSP响应程序主机名,还应指定resolver指令
# ssl_verify_client on;
# ssl_ocsp on;
# resolver 192.168.0.1;
-
ssl_ocsp_cache 指令设置存储用于OCSP验证的客户端证书状态的缓存名称和大小。该缓存在所有工作进程之间共享。具有相同名称的缓存可以在多个虚拟服务器中使用。
Syntax: ssl_ocsp_cache off | [shared:name:size];
Default: ssl_ocsp_cache off;
Context: http, server
-
ssl_ocsp_responder 指令用于设置客户端证书链的OCSP响应程序的URL,该选项仅在启用ssl_ocsp on;后才有效。如果设置了此参数,则Nginx将尝试从指定的URL获取OCSP响应程序,并将其用作验证客户端证书的有效性的替代方法。
Syntax: ssl_ocsp_responder url;
Default: —
Context: http, server
另外,http_ssl_module模块还提供了一些内置变量,可用于输出客户端证书中的信息,如下所示:
$ssl_server_name: 返回通过SNI(1.7.0)请求的服务器名称;
$ssl_alpn_protocol: 返回ALPN在SSL握手期间选择的协议,否则返回空字符串(1.21.4)
$ssl_protocol: 返回使用的SSL/TLS协议版本名称(例如,TLSv1.2)
$ssl_cipher: 返回使用的SSL/TLS加密套件名称
$ssl_ciphers: 返回客户端支持的密码列表(1.11.7)
$ssl_client_escaped_cert: 以PEM格式(urlencoded)返回已建立SSL连接的客户端证书(1.13.5);
$ssl_client_cert: 以PEM格式返回已建立的SSL连接的客户端证书,不推荐使用,建议使用 $ssl_client_escaped_cert 变量。
$ssl_client_raw_cert: 以PEM格式返回已建立SSL连接的客户端证书;
$ssl_client_fingerprint: 返回已建立SSL连接的客户端证书的SHA1指纹(1.7.1);
$ssl_client_i_dn: 根据RFC 2253(1.11.6)返回已建立SSL连接的客户端证书的“issuer DN”字符串;
$ssl_client_s_dn: 根据RFC 2253(1.11.6)返回已建立SSL连接的客户端证书的“subject DN”字符串;
$ssl_client_serial: 返回已建立SSL连接的客户端证书的序列号;
$ssl_client_sigalg: 返回已建立SSL连接的客户端证书的签名算法(1.29.3), 仅当使用OpenSSL 3.5或更高版本时才支持该变量。
$ssl_client_v_start 和 $ssl_client_v_end: 返回已建立SSL连接的客户端证书的开始和结束时间;
$ssl_client_v_remain: 返回已建立SSL连接的客户端证书剩余有效期(1.7.1);
$ssl_client_verify: 返回客户端证书验证结果,可能的值有:SUCCESS、FAILED: 证书未发送(1.5.7)、FAILED: 证书被拒绝(1.5.7)、NONE 或 FAILED: 未知原因。
$ssl_curve: 返回用于SSL握手密钥交换过程的协商曲线(1.21.5)。
$ssl_curves: 返回客户端支持的曲线列表(1.11.7)
$ssl_early_data: 如果使用TLS 1.3早期数据且握手未完成,则返回“1”,否则返回“”(1.15.3)。
$ssl_ech_outer_server_name: 如果接受TLS 1.3 ECH,则返回通过SNI请求的公共服务器名称,否则返回“”(1.29.4);
$ssl_ech_status: 返回TLS 1.3 ECH处理的结果:“FAQUE”、“EQUEND”、“GREASE”、“SUCCESS”或“NOT_TRIED”(1.29.4);
$ssl_session_id: 返回当前的SSL会话ID(1.13.0),注意其受到SSL会话缓存大小以及时间限制);
$ssl_session_reused: 如果SSL会话被重用,则返回“r”,或者“.”否则(1.5.11)。
$ssl_sigalg: 返回已建立SSL连接的服务器证书的签名算法(1.29.3)。
示例演示
接下来通过一个完整的配置案例,演示如何在 Nginx 中配置客户端与服务端双向认证,这是提升内部系统或API接口安全/渗透等级的重要手段。
步骤 01. 将之前生成的 CA 证书、服务器证书、客户端证书和私钥文件拷贝到 Nginx 的证书目录下。
cp /tmp/certs/ca.crt /tmp/certs/ca.key /usr/local/nginx/certs/
cp /tmp/certs/server.crt /tmp/certs/server.key /tmp/certs/server_encrypted.key /tmp/certs/ssl_password.txt /usr/local/nginx/certs/
cp /tmp/certs/client.crt /tmp/certs/client.key /usr/local/nginx/certs/
步骤 02. 在 Nginx 配置文件中添加 SSL 双向认证相关指令,创建一个新的配置文件。
tee /usr/local/nginx/conf.d/ssl_client_server.conf <<'EOF'
server {
listen 80;
# 监听 443 端口,启用 SSL
listen 443 ssl;
# 虚拟主机服务器名称
server_name server.weiyigeek.top;
charset utf-8;
default_type text/plain;
# 开起 HTTP/2 支持
http2 on;
# 日志文件
access_log /var/log/nginx/server.log main;
error_log /var/log/nginx/server.err.log debug;
# SSL 证书文件
ssl_certificate /usr/local/nginx/certs/server.crt;
ssl_certificate_key /usr/local/nginx/certs/server.key;
# 配置加密的 SSL 证书密钥文件(根据需求选择)
# ssl_certificate_key /usr/local/nginx/certs/server_encrypted.key;
# ssl_password_file /usr/local/nginx/certs/ssl_password.txt;
# 配置可信的 CA 证书文件
# ssl_trusted_certificate /usr/local/nginx/certs/ca.crt;
# 配置客户端证书验证
ssl_client_certificate /usr/local/nginx/certs/ca.crt;
ssl_verify_client on;
# 指定客户端证书到根证书的深度
ssl_verify_depth 2;
# 支持的 SSL/TLS 协议版本
ssl_protocols TLSv1.1 TLSv1.2 TLSv1.3;
# 支持的 SSL/TLS 加密套件
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:ECDHE:ECDH:AES:HIGH:EECDH+CHACHA20:EECDH+CHACHA20-draft:EECDH+AES128:RSA+AES128:EECDH+AES256:RSA+AES256:EECDH+3DES:RSA+3DES:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384:!NULL:!aNULL:!eNULL:!EXPORT:!PSK:!ADH:!DH:!DES:!MD5:!RC4;
# SSL 会话缓存
ssl_session_cache shared:SSL:10m;
# SSL 会话超时时间
ssl_session_timeout 10m;
# 优先使用服务器端支持的加密套件
ssl_prefer_server_ciphers on;
# 强制使用 HTTPS 访问
add_header Strict-Transport-Security "max-age=31536000;includeSubDomains;preload" always;
location / {
root /usr/local/nginx/html;
index index.html;
}
location /certificate {
return 200 '客户端证书验证结果:$ssl_client_verify\nssl_server_name: $ssl_server_name\nssl_protocol: $ssl_protocol\nssl_client_fingerprint: $ssl_client_fingerprint\nssl_cipher: $ssl_cipher\nssl_client_i_dn: $ssl_client_i_dn\nssl_client_s_dn: $ssl_client_s_dn\nssl_client_v_start: $ssl_client_v_start\nssl_client_v_end: $ssl_client_v_end\nssl_client_v_remain: $ssl_client_v_remain\nssl_session_id: $ssl_session_id\nssl_client_cert:\n $ssl_client_cert';
}
}
EOF
步骤 03. 配置完成后,重载 Nginx 服务。然后在客户端修改 hosts 文件,将域名指向服务器 IP,并通过浏览器访问 https://server.weiyigeek.top/ 进行测试。
nginx -s reload
# Linux
echo '10.20.172.214 server.weiyigeek.top' >> /etc/hosts
# Windows (C:\Windows\System32\drivers\etc\hosts)
# 10.20.172.214 server.weiyigeek.top
由于此时客户端尚未提供证书,服务器会拒绝请求,浏览器通常会显示 “400 Bad Request No required SSL certificate was sent” 的错误。
步骤 04. 接下来,需要将客户端证书导入到访问设备的证书存储中。首先,将 client.key 和 client.crt 合并生成为 PKCS12 格式(.pfx)的文件,方便在 Windows 等系统中导入。
openssl pkcs12 -export -inkey client.key -in client.crt -out client.pfx
生成过程中会提示设置导出密码。完成后,将 client.pfx 文件下载到客户端机器。在 Windows 系统中,可以双击该文件,或通过运行 certmgr.msc 打开证书管理器,将证书导入到“当前用户”的“个人”存储区。
步骤 05. 证书导入完毕后,再次使用浏览器访问 https://server.weiyigeek.top/。浏览器会检测到服务器要求客户端证书,并弹出窗口让用户选择已导入的证书。选择对应的客户端证书后继续。
步骤 06. 完成证书选择后,即可正常访问站点。为了验证双向认证成功并查看详细信息,可以访问 https://server.weiyigeek.top/certificate。该地址会返回一系列 SSL 变量信息,此时应能看到 $ssl_client_verify 变量的值为 SUCCESS,同时还能看到客户端证书的颁发者、主题、有效期等信息。
步骤 07. 若需要使用 API 测试工具(如 Postman 或 Apifox)访问配置了双向认证的接口,同样需要在工具中配置客户端证书。以 Apifox 为例,通常在请求的“证书”设置页,选择“P12”格式,然后上传 client.pfx 文件并输入生成时设置的密码即可。
知识扩展:获取服务端证书
有时候需要验证或获取服务端的公钥证书,可以使用 OpenSSL 命令连接并提取:
openssl s_client -connect server.weiyigeek.top:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' > ./server.crt
执行该命令后,当前目录下会生成一个 server.crt 文件,其中包含了服务端的证书内容。
通过以上步骤,便完成了 Nginx 服务端与客户端之间 SSL 双向认证的配置与验证。这种配置极大地增强了通信链路的安全性,适用于对运维/DevOps安全要求极高的内部服务或金融、政务等敏感业务场景。