3、HTTPS与TLS:TLS握手协议详解、证书链验证、在Nginx中配置HTTPS
说实话,HTTPS 现在已经是全栈开发的标配了。我见过不少团队,开发环境用 HTTP,上线前才匆匆忙忙配证书。结果呢?各种坑。今天咱们就把 TLS 握手、证书链、Nginx 配置这三个核心点彻底讲透。
3.1 TLS 握手协议:到底在握什么?
很多人以为 TLS 握手就是「客户端发个请求,服务端返回证书」。其实没那么简单。我当年第一次抓包看 TLS 握手,看到十几个来回的报文,直接懵了。
说白了,TLS 握手要干三件事:
- 身份验证:确认服务端(有时也包括客户端)是它声称的那个人
- 密钥协商:双方安全地商量出一个对称加密密钥
- 参数确认:确定用什么加密算法、什么协议版本
嗯,这里要注意,TLS 1.3 比 1.2 少了一个来回,但核心逻辑没变。咱们以最常见的 TLS 1.2 完整握手为例:
ClientHello → 客户端发送支持的TLS版本、加密套件列表、随机数
← ServerHello 服务端选定版本和加密套件、发送随机数
← Certificate 服务端发送证书链
← ServerHelloDone 服务端说「我发完了」
ClientKeyExchange → 客户端生成预主密钥,用服务端公钥加密后发送
ChangeCipherSpec → 客户端说「我要开始加密通信了」
Finished → 客户端发送加密后的握手摘要
← ChangeCipherSpec 服务端也切换加密
← Finished 服务端发送加密后的握手摘要
这里有个关键点:预主密钥。客户端生成的这个随机数,用服务端的公钥加密传输。只有持有私钥的服务端才能解密。我在项目中遇到过有人问:「那中间人拿到加密的预主密钥怎么办?」——他拿不到私钥,解不开。
核心结论:TLS 握手的本质,就是利用非对称加密安全地传递一个对称密钥。后续所有数据都用这个对称密钥加密,效率高得多。
3.2 证书链验证:信任是怎么传递的?
你想想看,浏览器凭什么相信服务端发来的证书?
答案就是证书链。我刚开始做安全时,觉得证书链就是个形式。直到有一次线上服务突然报证书错误,排查半天发现是中间证书没配全。从那以后,我对证书链再也不敢马虎。
证书链的结构是这样的:
根证书(CA根) → 中间证书 → 服务器证书(叶子证书)
验证过程分三步:
- 检查服务器证书:域名是否匹配、是否在有效期内、是否被吊销
- 验证签名链:用中间证书的公钥验证服务器证书的签名,再用根证书的公钥验证中间证书的签名
- 信任根证书:根证书必须存在于操作系统的受信任根证书列表中
这里有个常见的坑:证书链不完整。有些运维只配置了服务器证书,没配中间证书。某些浏览器会自动补全,但移动端或老旧系统就会报错。我曾经因为这个被客户投诉过——安卓 4.4 的设备死活连不上我们的 API。
我的建议:配置 HTTPS 后,用 openssl s_client -connect yourdomain.com:443 -showcerts 命令检查证书链是否完整。如果只返回一个证书,说明中间证书没配。
3.3 在 Nginx 中配置 HTTPS
好,理论讲完了,咱们来点实际的。Nginx 配置 HTTPS 其实不复杂,但细节决定成败。
先看一个最基础的配置:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/example.com.pem;
ssl_certificate_key /etc/nginx/ssl/example.com.key;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
location / {
proxy_pass http://backend;
}
}
嗯,这里要注意几个点:
- 证书文件:
.pem文件要包含完整的证书链。我习惯把服务器证书和中间证书拼在一起,服务器证书在上,中间证书在下 - 私钥文件:权限一定要设置为 600,只有 root 能读。我见过有人把私钥设成 644,结果被扫描工具扫出来了
- 协议版本:建议只开 TLSv1.2 和 TLSv1.3。SSLv3 和 TLSv1.0/1.1 都有已知漏洞,别用了
再来看一个更安全的配置:
server {
listen 443 ssl http2;
server_name example.com;
ssl_certificate /etc/nginx/ssl/fullchain.pem;
ssl_certificate_key /etc/nginx/ssl/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256;
ssl_prefer_server_ciphers on;
# 开启 OCSP Stapling
ssl_stapling on;
ssl_stapling_verify on;
resolver 8.8.8.8 8.8.4.4 valid=300s;
resolver_timeout 5s;
# 安全头
add_header Strict-Transport-Security "max-age=63072000" always;
add_header X-Content-Type-Options nosniff;
add_header X-Frame-Options DENY;
}
避坑指南:我曾经在配置 OCSP Stapling 时,忘了配 resolver 指令。结果 Nginx 无法解析 OCSP 响应器的域名,stapling 一直不生效。客户端每次连接都要自己去验证证书吊销状态,性能大打折扣。
配置完成后,记得用 nginx -t 测试配置是否正确。然后重启 Nginx:
nginx -t
systemctl reload nginx
最后,用 curl -vI https://example.com 验证一下。如果看到 SSL connection using TLSv1.3 之类的输出,说明配置成功了。
3.4 性能优化小贴士
HTTPS 确实比 HTTP 慢一点,但差距已经很小了。我做过压测,TLS 1.3 的握手延迟比 TLS 1.2 少了将近一个 RTT。如果你还在用 TLS 1.2,建议尽快升级。
另外,开启 http2 也能提升性能。HTTP/2 的多路复用特性,配合 HTTPS 使用效果更好。配置里加个 http2 就行,Nginx 会自动处理。
嗯,今天就先聊到这。下一章咱们会讲如何自动化管理证书,包括 Let's Encrypt 的自动续期。到时候再细聊。