本地客户端 与 Nginx 服务端 SSL 双向认证指令浅析与配置 描述: 上文《Nginx | 核心知识150讲,百万并发下性能优化之SSL证书签发与HTTPS加密传输实践笔记 》,介绍了如何在 Nginx 中配置 HTTPS 服务端单向认证,即证书配置在服务端,客户端不需要提供证书,但是这样的配置方式存在一定的安全隐患,因为客户端与服务端之间的通信并没有进行双向身份验证,有可能存在中间人攻击和数据泄露等问题。因此,为了保证数据在传输过程中不被泄露和篡改,且应用间的数据传输敏感的场景之下,通常会采用 SSL 双向认证的方式来进行身份验证,简单来说:服务端需要提供CA证书来对客户端证书进行身份验证的过程。
温馨提示:若文章代码块中存在乱码或不能复制,请联系作者,也可通过文末的阅读原文链接,加入知识星球中阅读,原文链接: https://articles.zsxq.com/id_wwnjk3rmwroa.html
同样的,在 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.; 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 中配置 客户端 与 Nginx 服务端双向认证。
示例演示
步骤 01.将上一篇《Nginx | 核心知识150讲,百万并发下性能优化之SSL证书签发与HTTPS加密传输实践笔记 》文章中生成的 ca、server、client 证书和私钥文件拷贝到 Nginx 的 /usr/local/nginx/certs/ 目录下,如下所示:
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 服务,并硬解析站点域名指向服务器 IP 地址,使用浏览器访问 https://server.weiyigeek.top/ 测试 https 是否配置成功,以及有何效果?由于客户端未发送证书,所以会提示 “ 400 Bad Request No required SSL certificate was sent” ,从而无法正常访问到业务系统,如下图所示:
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 weiyigeek.top-No required SSL certificate was sent图
步骤 04.接下来根据 client.key 和 client.crt 生成 pkcs12 格式的证书密钥打包文件,下载到机器后再将客户端证书导入到 Windows 系统证书管理器中,在 Windows 系统中,双击 client.pfx 文件导入到当前用户>个人/或者自动选择即可,或者 Ctrl + R 输入 certmgr.msc 打开证书管理器,选择“导入”,然后选择 client.pfx 文件即可。
$ openssl pkcs12 - export -inkey client.key - in client.crt -out client.pfx Enter Export Password: Verifying - Enter Export Password: weiyigeek.top-导入客户端证书到windows系统图
步骤 05.导入完毕后,在浏览器中访问 https://server.weiyigeek.top/ 如下图所示,选择导入的客户端证书。
weiyigeek.top-浏览器中选择客户端证书图
步骤 06.最后再次刷新访问 https://server.weiyigeek.top/certificate 测试一下客户端证书是否可正常访问站点,如图可以看到客户端证书验证结果为 SUCCESS,以及 SSL 加密套件以及客户端证书信息。
weiyigeek.top-验证客户端双向认证图
步骤 07.若需要使用类似于 Postman 或 Apifox 客户端工具访问,双向认证站点时,需要导入客户端证书密码或者p12格式证书,这里以 Apifox 客户端工具为例,如下所示:
weiyigeek.top-在Apifox 客户端工具中配置认证图
知识扩展:有时候生产环境中想获取到服务端的公钥信息,可以使用 openssl s_client 命令进行获取:
$ openssl s_client -connect server.weiyigeek.top:443 </dev/null | sed -ne '/-BEGIN CERTIFICATE-/,/-END CERTIFICATE-/p' >./server.crt depth=0 C = CN, ST = Chongqing, L = Chongqing, O = WeiyiGeek, OU = Nginx Server, CN = server.weiyigeek.top verify error:num=20:unable to get local issuer certificate verify return :1 depth=0 C = CN, ST = Chongqing, L = Chongqing, O = WeiyiGeek, OU = Nginx Server, CN = server.weiyigeek.top verify error:num=21:unable to verify the first certificate verify return :1 depth=0 C = CN, ST = Chongqing, L = Chongqing, O = WeiyiGeek, OU = Nginx Server, CN = server.weiyigeek.top verify return :1 DONE $ ls -alh server.crt -rw-r--r-- 1 root root 1.5K Dec 16 11:33 server.crt 至此,Nginx 服务端与客户端双向认证配置完成,是不是很简单呀,你也赶快试试吧!
阅读原文:原文链接
该文章在 2025/12/19 10:17:03 编辑过