HTTPS 与 TLS
这一课你将能做到
- 用三句话说明 HTTPS 相对纯 HTTP 防住了什么:窃听、篡改、冒充,并说出每一条靠 TLS 的哪个机制
- 画出「浏览器 ──TLS──► Nginx:443 → 内网明文 → Spring:8080」的链路,解释为什么证书装在 Nginx 而业务仍是明文
- 用浏览器锁图标与
curl -v 确认证书域名与 TLS 版本,知道本地自签证书何时用 -k、为什么生产绝不能用
为什么要学
上一课说过:Authorization、Cookie、手机号——在纯 HTTP 上全是明信片,路径上的任何节点(公共 Wi-Fi、运营商、代理)都能读能改。全栈开发的默认共识是「公网必须 HTTPS」,但配了 HTTPS 仍会踩坑:证书过期、混合内容被拦、反代后应用以为自己是 HTTP 导致 Secure Cookie 失效、重定向死循环。
面试必考「HTTPS 握手过程」「对称加密和非对称加密为什么都用」,本课建立 TLS 直觉,证书申请与 Nginx 细节配置交给 nginx-essentials 的 SSL 课。
先建立图像
| 明文 HTTP 的风险 | TLS 的对策 |
|---|
| 旁路窃听 body、Cookie、密码 | 对称加密保护应用数据(密钥只有通信双方知道) |
| 中间人篡改 HTML/JSON/JS | 完整性校验(AEAD 加密套件自带防篡改) |
| 假站点骗你输密码 | 证书链 + 系统信任的 CA 证明「这公钥真属于该域名」 |
简化握手直觉(不必背字节序):
1. Client Hello:我支持的 TLS 版本与加密套件列表
2. Server Hello + 证书:选定版本套件,出示证书(含域名与公钥)
3. 客户端校验证书:域名对得上?链条到系统信任的根?没过期?
4. 密钥协商(非对称算法只用来安全地商量出一个对称会话密钥)
5. 之后所有 HTTP 请求/响应都在这条加密通道里飞
为什么对称 + 非对称混用:对称加密快但「密钥怎么安全送到对方」是无解问题;非对称加密能解决传输但太慢。所以非对称只用来协商,对称负责干活。
本站生产拓扑:
浏览器 ──TLS──► Nginx :443(证书装在这里,TLS 在这里结束)
├─ proxy_pass http://127.0.0.1:8080 /api/* → Spring
└─ proxy_pass http://127.0.0.1:3000 页面 → 前端服务
TLS 终止在 Nginx:门岗持证,内院 Spring 只监听本机/内网,公网摸不到 8080——内网明文不等于整条链路暴露。
跟着做
步骤 1:看锁与证书
- 打开
https://coderoadmap.cn,点地址栏锁图标 → 「连接安全」→ 证书
- 记录三项:颁发给(域名/SAN)、颁发者(如 Let's Encrypt R3)、有效期
应看到:域名与地址栏一致、有效期在未来。再随便开一个 http:// 的老站对比——地址栏的「不安全」提示就是在告诉你这层保护缺失。
步骤 2:curl 看协商出的 TLS 版本
curl -vI https://coderoadmap.cn 2>&1 | findstr /i "SSL TLS subject expire"
# Linux/macOS:curl -vI https://coderoadmap.cn 2>&1 | grep -iE "SSL|TLS|subject"
应看到类似 SSL connection using TLSv1.3 的行——TLS 1.3 握手只需 1-RTT,比 1.2 更快更安全。若看到 TLSv1.0/1.1 说明服务端配置过旧,可以提工单了。
步骤 3:本地自签证书 + -k 的正确用法
给本地 Spring Boot 配自签证书(开发调试 HTTPS 接口用):
# keytool 生成自签证书(JDK 自带),密码均为 changeit
keytool -genkeypair -alias local -keyalg RSA -keysize 2048 \
-storetype PKCS12 -keystore keystore.p12 -validity 365 \
-dname "CN=localhost" -storepass changeit
# application.yml
server:
port: 8443
ssl:
key-store: classpath:keystore.p12
key-store-password: changeit
key-store-type: PKCS12
# 自签证书不在系统信任列表,curl 会报证书校验失败
curl -v https://localhost:8443/api/hello
# -k 跳过校验:只证明「端口通」,不证明「连的是真服务器」
curl -vk https://localhost:8443/api/hello
-k 仅限本地自签排错;生产教用户点「高级→继续访问」等于拆掉防冒充防线。
步骤 4:混合内容自检
页面是 https://,却用 http:// 拉脚本/API,浏览器直接拦截。自查方法:
- 打开站点任意 HTTPS 页面 → F12 Console,搜
Mixed Content
- Network 面板看有无
blocked: 开头的请求
修复原则:页面内资源与 API 全部用相对路径或 https://。新版浏览器还会把 http:// 导航自动升级,但别依赖浏览器的仁慈。
步骤 5:反代后的「我是不是 HTTPS」陷阱
应用若根据 request.isSecure() 生成重定向或决定 Cookie 的 Secure 标志,在 Nginx 反代后会判断错误——外网明明是 HTTPS,应用却以为自己是 HTTP。两处配合修复:
# Nginx 侧:把真实协议传给上游
proxy_set_header X-Forwarded-Proto $scheme;
# Spring 侧:信任 Forwarded 头
server:
forward-headers-strategy: framework # 或 native
验证:写一个 @GetMapping("/proto") 返回 request.isSecure() 与 X-Forwarded-Proto 头的值,直连 8080 与走 Nginx 各 curl 一次对比。
步骤 6:301 强制 HTTPS(Nginx 侧)
server {
listen 80;
server_name coderoadmap.cn;
return 301 https://$host$request_uri; # 永久跳转,状态码课的 301 在这里用上了
}
curl -i http://coderoadmap.cn/api/tracks
# 期望:HTTP/1.1 301 + Location: https://...
# 加 -L 让 curl 自动跟随跳转
curl -sIL http://coderoadmap.cn/api/tracks | findstr /i "HTTP Location"
步骤 7:openssl 摘要(可选,便于脚本监控)
echo | openssl s_client -servername coderoadmap.cn -connect coderoadmap.cn:443 2>/dev/null | openssl x509 -noout -subject -dates
输出 subject 与 notAfter 与浏览器锁图标同源,可写进证书过期监控脚本。
讲清楚要点
- 因为 TLS 提供机密性与完整性,所以密码、Token、个人信息的传输绝不能走公网明文 HTTP——本地开发可以 HTTP,上线必须 HTTPS,这是行业底线而非建议。
- 因为证书证明「公钥属于该域名」,所以证书的域名/SAN 必须覆盖用户实际访问的 host——只签
example.com 却让用户访问 www.example.com 会直接报警。
- 因为常见架构在边缘终止 TLS,所以「内网 Spring 是 HTTP」不等于链路裸奔——前提是 8080 端口不映射公网、防火墙只放行本机/内网访问。
- 因为非对称加密只负责协商会话密钥,所以 HTTPS 的性能损耗远比想象小——TLS 1.3 一个 RTT 完成握手,配合会话复用,开销集中在首次连接。
- 因为
-k/忽略告警关闭的是身份校验,所以它只证明链路可达,不证明对面是谁——自动化脚本、CI 里禁用校验会把中间人风险重新引入。
- 因为私钥泄露等于别人可以冒充你的站点,所以证书私钥要当机密管理——权限收敛、不进 Git 仓库、支持快速吊销重签;用 Let's Encrypt 自动续期把人肉操作降到零。
你可能会懵的地方
-
HTTPS 是不是另一种协议,方法状态码都变了?
完全没变。方法、状态码、头的语义与 HTTP 一致,变的只是下面多了一层 TLS 加密通道——这也是本 track 先讲语义再讲传输的原因。
-
为什么不给 Spring 也配证书?
可以(零信任架构里叫 mTLS),但运维成本高。多数站点「边缘终止 + 内网隔离」已够;银行级内网通信才普遍上 mTLS。
-
证书过期会怎样?
浏览器大红警告页,curl 直接报 certificate has expired,App 可能拒绝连接。用 acme.sh/certbot 自动续期 + 过期监控是运维基本功——凌晨证书过期导致全站不可用是经典事故。
-
HSTS 是干什么的?
响应头 Strict-Transport-Security: max-age=31536000; includeSubDomains 让浏览器记住「接下来一年访问本域只许 HTTPS」,即使用户手输 http:// 也内部强制升级。注意:一旦下发且 max-age 很长,回退 HTTP 会很痛苦——先保证全站 HTTPS 稳定,再上 HSTS。
-
HTTP/2 是不是必须 HTTPS?
浏览器侧几乎总是(明文 h2c 存在但主流浏览器不支持)。所以下一课讲 HTTP/2 时默认你已经有证书。
错法对照
| 错法 | 问题 | 正解 |
|---|
登录页 HTTPS,表单提交却 POST 到 http:// | 密码在最后一步裸奔,前面加密全白费 | 全站 HTTPS,接口用同源相对路径 |
| 生产用自签证书让用户点「继续访问」 | 防冒充形同虚设,还训练用户无视警告 | 公共 CA(Let's Encrypt 免费)签发真证书 |
| 以为「有锁」就能防 XSS/SQL 注入 | TLS 只保护传输层,应用层漏洞照单全收 | 传输安全与应用安全分开治理 |
| 把证书私钥提交进 Git 仓库 | 私钥一旦泄露必须紧急吊销重签 | 私钥只在部署机/密钥管理服务;仓库只放 example 配置 |
反代后不信任 X-Forwarded-Proto | 应用误判协议:Secure Cookie 失效、重定向死循环 | Nginx 传头 + Spring forward-headers-strategy 配对 |
卡点急救
- 浏览器报
ERR_CERT_COMMON_NAME_INVALID:证书域名与访问域名不匹配。检查证书 SAN 是否覆盖 www/裸域;用 curl -vI 的 subject 行确认实际下发的证书(可能 Nginx 配了默认 server 的证书)。
- 配了 HTTPS 却重定向死循环:典型是「Nginx 80→443 跳转 + 应用内部再判 HTTP 就跳 HTTPS」互相打架。理清唯一跳转点在 Nginx,应用侧信任
X-Forwarded-Proto 后不再自行跳转。
- curl 报
SSL certificate problem: unable to get local issuer certificate:本机缺中间证书或系统时间不对。先校对系统时间;企业内网代理会做 SSL 解密,需导入公司根证书或临时 -k(仅诊断)。
面试常问
-
HTTPS 握手过程?对称加密和非对称加密分别用在哪?
客户端发 Client Hello(版本+套件)→ 服务端回 Server Hello + 证书 → 客户端验证证书链与域名 → 双方协商出对称会话密钥 → 之后用对称加密通信。非对称(RSA/ECDHE)只用于验证身份与协商密钥,因为它慢;对称(AES)用于数据传输,因为它快。TLS 1.3 压缩到 1-RTT。
-
中间人攻击是什么?HTTPS 如何防住?
攻击者拦截通信冒充服务端(或双向冒充),转发并篡改数据。防住的机制:证书由 CA 签名、链式校验到系统信任根,攻击者拿不到可信证书;会话密钥协商保证偷听者也算不出密钥;AEAD 套件保证任何篡改都会被完整性校验发现。
-
为什么公司内网服务之间还用 HTTP?这样安全吗?
TLS 已在边缘终止,内网流量不出机房,攻击面小;全内网上 mTLS 成本高。前提条件:上游端口不暴露公网、安全组/防火墙限制来源。若要求更高,服务网格里做 mTLS。
自测三问
- 纯 HTTP 的三类经典风险是什么?HTTPS 分别靠什么机制对应?
- 画出本站的 TLS 终止点,说明为什么 Spring 的 8080 可以是明文,需要满足什么前提。
curl -k 什么时候可以用?什么时候绝对不行?为什么?
小结一句话
HTTPS = HTTP 跑在 TLS 上:对称加密防窃听、完整性校验防篡改、证书链防冒充——证书装在 Nginx 边缘,业务仍在内网应用。
延伸