nginx

关注公众号 jb51net

关闭
首页 > 网站技巧 > 服务器 > nginx > Nginx SSL/TLS双向认证

Nginx SSL/TLS双向认证实战小结

作者:正规子群

本文手把手教你用OpenSSL生成CA、服务器和客户端证书,配置Nginx实现客户端与服务器互相验证身份,助你快速搭建安全可信的API通信

1. 项目概述:从HTTP到HTTPS的安全升级

最近在部署一个内部API网关时,遇到了一个典型的需求:外部服务调用我们的接口,部分场景要求高安全性,不仅需要加密传输,还要验证调用方的身份。这让我重新梳理了一遍SSL/TLS证书的配置,特别是单向认证和双向认证的区别与实现。很多朋友在初次接触 https 时,往往止步于“让浏览器不显示不安全提示”,但对于后端服务间的通信,尤其是金融、物联网或内部微服务调用,双向认证才是构建可信通信链的关键。这次,我就以最常用的Nginx为例,把手动生成证书、配置单向/双向认证,以及其中容易踩坑的细节完整走一遍。

你会发现,所谓的“认证”,核心就是一套基于公钥密码学的“信任”验证机制。单向认证是服务器向客户端证明“我是我”,而双向认证则是客户端和服务器互相证明“你是你”。理解了这个本质,无论是用OpenSSL命令行生成证书,还是配置Nginx的 ssl_ 系列指令,都会清晰很多。本文不会停留在“复制粘贴配置就能用”的层面,我会带你理解每一个参数背后的意义,以及在不同生产环境(如云服务器、容器化部署)下的适配要点。

2. SSL/TLS与证书基础:信任链是如何建立的

在动手配置之前,我们得先搞清楚几个核心概念,否则配置文件的每一行都会是黑盒操作。

2.1 核心角色:CA、证书、公钥与私钥

你可以把数字证书想象成一张由权威机构(CA)颁发的“网络身份证”。这张身份证上至少包含以下信息:持有者(服务器或客户端)的名称、持有者的公钥、颁发者(CA)的名称、有效期以及CA的 数字签名 。

当客户端(如浏览器)访问你的 https 站点时,它会收到你的服务器证书。客户端会做两件事:1. 用证书里CA的公钥(来自客户端信任的根证书/中间证书链)去验证证书上CA签名的真实性。2. 检查证书中的域名是否与实际访问的域名一致、是否在有效期内。全部通过,才认为服务器可信。

2.2 单向认证 vs. 双向认证:谁验证谁?

这是本文的重点,两者的流程差异决定了配置的复杂度。

简单说,单向认证是“客户查服务器的身份证”,双向认证是“客户和服务器互相查身份证”。

3. 实战准备:使用OpenSSL生成证书链

在生产环境,服务器证书通常从受信的商业CA(如DigiCert, Let‘s Encrypt)或企业内私有CA获取。但为了学习和测试,我们可以自己扮演CA,用OpenSSL生成全套证书。这能让你透彻理解整个链条。

注意:自签名CA颁发的证书在互联网上不会被公共浏览器默认信任,仅用于内部测试或开发环境。

3.1 创建私有根CA

首先,我们创建一个自己的根CA。

# 1. 创建用于存放CA文件的目录结构
mkdir -p myCA/private myCA/certs myCA/newcerts
cd myCA
touch index.txt
echo 1000 > serial

# 2. 生成根CA的私钥(建议使用强密码保护,这里为演示使用-nodes去除密码)
openssl genrsa -out private/ca.key 2048

# 3. 生成根CA的自签名证书
openssl req -new -x509 -days 3650 -key private/ca.key -out certs/ca.crt \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyTestOrg/CN=MyTestRootCA"

参数解释 :

现在, certs/ca.crt 就是我们的根证书,需要将其导入到需要信任该CA的客户端或服务器系统中。

3.2 签发服务器证书

现在用我们自己的CA为服务器 api.mytest.com 签发证书。

# 1. 生成服务器私钥
openssl genrsa -out server.key 2048
# 2. 生成证书签名请求(CSR)
openssl req -new -key server.key -out server.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyTestOrg/CN=api.mytest.com"
# 3. 使用根CA为CSR签名,生成服务器证书
openssl ca -in server.csr -out server.crt -cert certs/ca.crt -keyfile private/ca.key -days 365

执行第3步时,OpenSSL会交互式地让你确认签发,两次输入 y 即可。生成的 server.crt 和 server.key 就是用于Nginx配置的服务器证书和私钥。

3.3 签发客户端证书(为双向认证准备)

流程与签发服务器证书几乎一样,只是主题信息不同。

# 1. 生成客户端私钥
openssl genrsa -out client.key 2048

# 2. 生成客户端CSR
openssl req -new -key client.key -out client.csr \
  -subj "/C=CN/ST=Beijing/L=Beijing/O=MyTestOrg/CN=MyTestClient"

# 3. 使用根CA签发客户端证书
openssl ca -in client.csr -out client.crt -cert certs/ca.crt -keyfile private/ca.key -days 365

此外,客户端证书通常需要转换为PKCS#12格式( .p12 或 .pfx ),以便导入到浏览器或Java等应用的密钥库中。

# 将客户端证书和私钥打包为p12格式,需要设置导入密码
openssl pkcs12 -export -in client.crt -inkey client.key -out client.p12 -name "MyClientCert"

4. Nginx配置详解:从单向到双向

假设我们已经有了证书文件: server.crt , server.key , ca.crt (根证书), client.crt (客户端证书)。

4.1 基础单向认证配置

这是配置 https 站点的起点。

server {
    listen 443 ssl; # 监听443端口并启用SSL
    server_name api.mytest.com;
    # 1. 指定服务器证书和私钥(必需)
    ssl_certificate /path/to/your/server.crt;
    ssl_certificate_key /path/to/your/server.key;
    # 2. 优化SSL协议和密码套件(强烈建议)
    ssl_protocols TLSv1.2 TLSv1.3; # 禁用不安全的SSLv2, SSLv3, TLSv1.0, TLSv1.1
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_prefer_server_ciphers on;
    # 3. 启用SSL会话缓存,提升性能
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;
    # 你的应用配置
    location / {
        proxy_pass http://backend_server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}
# 将HTTP请求重定向到HTTPS(标准做法)
server {
    listen 80;
    server_name api.mytest.com;
    return 301 https://$server_name$request_uri;
}

关键点解析 :

4.2 升级为双向认证配置

在单向认证的基础上,增加验证客户端证书的配置。

server {
    listen 443 ssl;
    server_name api.mytest.com;

    ssl_certificate /path/to/your/server.crt;
    ssl_certificate_key /path/to/your/server.key;

    # 1. 指定用于验证客户端证书的CA证书(必需)
    ssl_client_certificate /path/to/your/ca.crt;

    # 2. 设置客户端证书验证模式(必需)
    ssl_verify_client on; # 或 `optional` | `optional_no_ca`
    # `on`: 必须提供且验证通过的有效客户端证书。
    # `optional`: 客户端可以提供证书,如果提供则验证。
    # `optional_no_ca`: 客户端可以提供证书,即使无法用`ssl_client_certificate`验证也接受。

    # 3. 设置验证深度(可选)
    ssl_verify_depth 2; # 验证链深度,如果客户端证书由中间CA签发,需要适当调大。

    # 优化配置(同单向认证)
    ssl_protocols TLSv1.2 TLSv1.3;
    ssl_ciphers ECDHE-RSA-AES128-GCM-SHA256:ECDHE:ECDH:AES:HIGH:!NULL:!aNULL:!MD5:!ADH:!RC4;
    ssl_prefer_server_ciphers on;
    ssl_session_cache shared:SSL:10m;
    ssl_session_timeout 10m;

    location / {
        # 4. 将客户端证书信息传递给后端应用(非常有用!)
        proxy_set_header X-SSL-Client-Cert $ssl_client_cert;
        proxy_set_header X-SSL-Client-Verify $ssl_client_verify;
        proxy_set_header X-SSL-Client-S-DN $ssl_client_s_dn; # 证书主题
        proxy_set_header X-SSL-Client-I-DN $ssl_client_i_dn; # 颁发者

        proxy_pass http://backend_server;
        proxy_set_header Host $host;
        proxy_set_header X-Real-IP $remote_addr;
    }
}

核心配置解读 :

  1. ssl_client_certificate :这里放置的是 签发客户端证书的CA的证书 (即我们的 ca.crt )。Nginx用它来验证客户端证书的签名是否可信。可以包含多个CA的证书,用于验证来自不同CA的客户端。
  2. ssl_verify_client on; :这是开启双向认证的开关。设为 on 后,没有有效客户端证书的连接会被Nginx拒绝,并返回 400 Bad Request 错误。
  3. proxy_set_header X-SSL-Client-* :这是双向认证的精髓之一。验证通过后,Nginx可以将客户端证书的详细信息(PEM格式的证书内容、验证结果、主题信息等)通过HTTP头传递给后端的Java/Python/Go等应用。后端应用可以基于这些信息做更细粒度的权限控制,例如根据证书主题中的 CN 字段识别具体用户或设备。

5. 测试与验证:确保配置生效

配置完成后,重启Nginx ( nginx -s reload ),必须进行验证。

5.1 测试单向认证

使用 curl 命令测试基础 https 是否工作。

# 测试单向认证(不验证服务器证书,仅用于测试)
curl -k https://api.mytest.com/

# 携带根证书,严格验证服务器证书(模拟浏览器行为)
curl --cacert /path/to/myCA/certs/ca.crt https://api.mytest.com/

第一条命令的 -k 参数会忽略证书验证,能快速确认服务是否监听。第二条命令使用我们自签的CA证书去验证服务器证书,如果成功,说明单向认证的信任链是完整的。

5.2 测试双向认证

测试双向认证需要客户端提供证书和私钥。

# 使用客户端证书和私钥进行访问
curl --cert ./client.crt --key ./client.key \
     --cacert /path/to/myCA/certs/ca.crt \
     https://api.mytest.com/

# 如果不提供客户端证书,应该被拒绝
curl --cacert /path/to/myCA/certs/ca.crt https://api.mytest.com/
# 预期返回 400 Bad Request: The SSL certificate error

第一条命令成功,第二条命令失败,就证明双向认证配置正确生效了。

5.3 浏览器测试双向认证

对于Web应用,你可能需要在浏览器中测试。

  1. 将生成的 client.p12 文件导入到你的浏览器或操作系统的证书存储中。
  2. 访问 https://api.mytest.com 。
  3. 浏览器会弹出一个对话框,让你选择一个客户端证书进行认证。选择你导入的 MyTestClient 证书。
  4. 如果证书有效且被Nginx信任,你就能正常访问网站;否则会被拒绝。

6. 生产环境进阶考量与排坑指南

把测试环境的配置搬到生产环境,会遇到一系列新问题。

6.1 证书管理:自动化与续期

6.2 Nginx配置优化与安全加固

6.3 双向认证的常见陷阱

  1. ssl_client_certificate 配置错误 :

    • 问题 :这里应该放的是 CA证书 ,用于验证客户端证书的签名。很多人误将客户端证书放在这里。
    • 现象 :客户端连接时,Nginx报错 “client certificate verify failed” 或 “unable to get local issuer certificate” 。
    • 排查 :使用命令 openssl verify -CAfile /path/to/ca.crt client.crt 来验证你的客户端证书是否能被指定的CA证书正确验证。
  2. 客户端证书格式问题 :

    • 问题 :客户端提供的证书格式不对,或者证书链不完整。
    • 现象 :浏览器或客户端工具提示证书无效。
    • 解决 :确保客户端拥有完整的证书链(客户端证书+中间CA证书)。对于PEM格式,通常是多个 -----BEGIN CERTIFICATE----- 块拼接在一个文件里。
  3. 性能影响 :

    • 双向认证的TLS握手比单向认证更耗时,因为多了一次证书传输和验证。对于高并发场景,要特别注意 ssl_session_cache 的调优,并考虑是否所有接口都需要双向认证。可以通过Nginx的 location 块进行精细控制,只为特定路径开启 ssl_verify_client on; 。
  4. 后端应用获取证书信息 :

    • 如前所述,通过 $ssl_client_* 变量将证书信息传递给后端是标准做法。但要注意,证书信息(特别是 $ssl_client_cert )可能包含换行符,直接放在HTTP头里有时会出问题。一种常见的做法是后端从特定的头(如 X-SSL-Client-Cert )中读取PEM证书内容,然后进行解析和验证。

6.4 在云环境和容器中的配置

配置SSL证书,尤其是双向认证,是一个对细节要求极高的工作。从理解原理、生成证书、编写配置到测试排错,每一步都需要仔细。我个人的经验是,遇到问题时,先使用 openssl s_client -connect host:port -cert ... -key ... 命令进行底层连接测试,它能提供最详细的握手和证书信息,比直接调试Nginx或应用更高效。把证书信任链理清楚,把Nginx的错误日志级别调到 info 或 debug ,大部分问题都能迎刃而解。

到此这篇关于Nginx SSL/TLS双向认证实战小结的文章就介绍到这了,更多相关Nginx SSL/TLS双向认证内容请搜索脚本之家以前的文章或继续浏览下面的相关文章希望大家以后多多支持脚本之家!

您可能感兴趣的文章:
阅读全文