大家好,我是程序员柏风,AI 最全的 Vibecoding 学习地图 http://coderoadmap.cn 作者。
网络安全基础
这一课你将能做到
- 用
openssl s_client和curl -v完整走一遍 TLS 握手过程,说出每一步在交换什么 - 在 Nginx 上配置一套合格的 HTTPS(TLS 1.2+、强加密套件、HTTP 强制跳转、HSTS),并用 SSL Labs 验证评分
- 解释 VLAN、安全组、防火墙在网络分段中的作用,画出本站(Nginx → Spring Boot → MySQL/Redis)的分段拓扑
为什么要学
你的 Java 应用不是活在真空里:浏览器到 Nginx、Nginx 到 Spring Boot、Spring Boot 到 MySQL/Redis,每一跳都是网络。不懂网络,就看不懂「为什么开了 HTTPS 还被劫持」「为什么数据库不能暴露公网」这些基本问题。
人话:网络是敌我分界线。TLS 保证「你和我说的话别人听不懂」;防火墙和分段保证「不该通的路一律不通」。本站的事故里,一半以上本质是网络配置问题——Redis 裸奔公网、MySQL 0.0.0.0 监听、TLS 版本太老被降级攻击。
先建立图像
一次 HTTPS 请求的完整旅程:
浏览器 ──① DNS 查询──→ DNS 服务器 (拿到 IP)
│
├──② TCP 三次握手──→ Nginx (443 端口)
│ SYN → SYN+ACK → ACK
│
├──③ TLS 握手──→ Nginx
│ ClientHello (支持的 TLS 版本/套件)
│ ServerHello + 证书链
│ 密钥交换 → 会话密钥
│
├──④ 加密 HTTP 请求──→ Nginx
│
└── Nginx ──⑤ 反向代理(明文HTTP)──→ Spring Boot :8080
│
├──⑥ TCP──→ MySQL :3306
└──⑦ TCP──→ Redis :6379
网络分段的正确姿势:
┌─ 公网 ────────────────────────────┐
│ 只暴露 Nginx 的 80/443 │
└──────────┬────────────────────────┘
│ (DMZ 区)
┌──────────▼────────────────────────┐
│ Spring Boot :8080 │
│ 安全组:只允许 Nginx 服务器 IP 访问 │
└──────────┬────────────────────────┘
│ (内网数据区)
┌──────────▼────────────────────────┐
│ MySQL :3306 / Redis :6379 │
│ 安全组:只允许应用服务器 IP 访问 │
│ bind 127.0.0.1 或内网网卡 │
└───────────────────────────────────┘
跟着做
步骤 1:观察 TLS 握手
# 看完整握手过程:协议版本、加密套件、证书链
openssl s_client -connect coderoadmap.cn:443 -servername coderoadmap.cn
# 重点看这几行输出:
# Protocol : TLSv1.3
# Cipher : TLS_AES_256_GCM_SHA384
# Verify return code: 0 (ok)
用 curl 看简化版:
curl -vI https://coderoadmap.cn 2>&1 | findstr /C:"SSL connection" /C:"subject:" /C:"issuer:"
-servername 参数是 SNI(Server Name Indication):一台 Nginx 托管多个域名证书时,靠它告诉服务器「我要访问哪个域名」。
步骤 2:Nginx 配置合格的 HTTPS
server {
listen 443 ssl;
server_name coderoadmap.cn;
# 证书(用 Let's Encrypt 免费证书)
ssl_certificate /etc/letsencrypt/live/coderoadmap.cn/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/coderoadmap.cn/privkey.pem;
# 只开 TLS 1.2 和 1.3,关掉有漏洞的 SSLv3/TLS1.0/1.1
ssl_protocols TLSv1.2 TLSv1.3;
# 强加密套件,禁用弱算法
ssl_ciphers 'ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384';
ssl_prefer_server_ciphers off; # TLS1.3 下让客户端选
# HSTS:告诉浏览器以后只走 HTTPS,防降级
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;
location / {
proxy_pass http://127.0.0.1:8080;
proxy_set_header X-Forwarded-Proto $scheme; # 让后端知道原始协议
}
}
# HTTP 强制跳转 HTTPS
server {
listen 80;
server_name coderoadmap.cn;
return 301 https://$host$request_uri;
}
改完验证:
nginx -t && nginx -s reload
curl -I http://coderoadmap.cn # 应返回 301
再去 ssllabs.com/ssltest 测一下,目标 A 或 A+。
步骤 3:验证 TLS 版本下限
# 这两条应该失败(握手被拒绝),说明老协议已关闭
openssl s_client -connect coderoadmap.cn:443 -tls1_1
openssl s_client -connect coderoadmap.cn:443 -ssl3
# 这条应该成功
openssl s_client -connect coderoadmap.cn:443 -tls1_3
步骤 4:收紧内网服务监听
MySQL 的 my.cnf:
[mysqld]
# 如果 MySQL 和 Spring Boot 同机,只监听回环
bind-address = 127.0.0.1
Redis 的 redis.conf:
bind 127.0.0.1
requirepass 一个足够长的随机密码
# 重命名危险命令,防被拿到后一键 FLUSHALL
rename-command FLUSHALL ""
rename-command CONFIG ""
验证端口暴露面:
# Windows
netstat -ano | findstr LISTENING
# 确认 3306、6379 只绑在 127.0.0.1,不是 0.0.0.0
步骤 5:用防火墙做分段
云服务器用安全组;裸机用防火墙。以 Windows/Linux 通用思路为例,规则分三层:
入站规则(按优先级):
1. 允许 任意来源 → 本机 80/443 (Nginx)
2. 允许 Nginx 服务器IP → 本机 8080 (Spring Boot)
3. 允许 办公网IP段 → 本机 22 (SSH 管理)
4. 拒绝 其余所有入站
原则一句话:默认拒绝,按需放行,而不是默认放行、出事再堵。
讲清楚要点
-
TLS 握手的本质:用非对称加密(证书里的公钥)安全地协商出一个对称会话密钥,之后的数据用对称加密传输(快)。证书解决「你是谁」,密钥交换解决「话不被偷听」。
-
HTTPS 不等于绝对安全:它能防传输窃听和篡改,但防不了「两端之一被攻破」「证书签发环节被骗(误签 CA)」「用户无视证书警告点继续」。
-
网络分段(Segmentation)是横向移动的克星:攻击者打进 Nginx 服务器,不代表能摸到 MySQL。每段之间默认不通,让一次入侵停在一段。
-
bind 地址是第一道闸:
bind 127.0.0.1意味着这个端口物理上不可能被外网访问,比任何防火墙规则都可靠。Redis 未授权访问事件的根因几乎全是bind 0.0.0.0且无密码。 -
HSTS 防降级攻击:没有 HSTS 时,攻击者可以把用户的 HTTPS 请求降级成 HTTP 再窃听(SSL Strip)。HSTS 让浏览器记住「这个站只走 HTTPS」。
你可能会懵的地方
-
Nginx 到 Spring Boot 之间要不要也上 HTTPS? 同一台机器或同一内网可信段,通常走明文 HTTP 即可(性能考虑);跨机房、跨 VPC、零信任架构下建议 mTLS(双向 TLS)。判断标准:这条链路上的流量会不会经过你不信任的网络设备。
-
TLS 1.3 和 1.2 差在哪? 1.3 握手从 2 个 RTT 减到 1 个(更快),删掉了所有已知不安全的算法(RSA 密钥交换、CBC 模式、SHA-1),强制前向保密。能开就开。
-
证书免费和付费有区别吗? 加密强度一样。区别在:有效期(Let's Encrypt 90 天需自动续期 vs 付费 1 年)、是否支持通配符/多域名、赔付保险。技术上免费证书完全够用。
-
防火墙和安全组是一回事吗? 作用相同,位置不同。安全组是云平台在 hypervisor 层做的(虚拟机外面),防火墙(iptables/Windows Firewall)跑在操作系统里。纵深防御:两层都配。
-
为什么 HTTP 跳转用 301 而不是 302? 301 是永久跳转,浏览器会缓存,下次直接访问 HTTPS,减少一次可被劫持的 HTTP 请求。配合 HSTS 双保险。
错法对照
| 错法 | 问题 | 正解 |
|---|---|---|
ssl_protocols 包含 TLSv1.0 | POODLE 等降级攻击 | 只留 TLSv1.2 TLSv1.3 |
Redis bind 0.0.0.0 无密码 | 公网任何人可读写,勒索高发 | bind 127.0.0.1 + requirepass |
| 数据库安全组对 0.0.0.0/0 开放 | 全网可探测爆破 | 只放行应用服务器 IP |
| 只配 HTTPS 不配 HSTS 和跳转 | 用户仍可能走 HTTP 被窃听 | 301 跳转 + HSTS 头 |
Nginx 把 X-Forwarded-* 透传给不可信后端 | 伪造来源 IP 绕过白名单 | 内网可信才透传,或在后端校验 |
卡点急救
-
证书配好浏览器还报「不安全」:九成是证书链不完整——
ssl_certificate要用 fullchain.pem(服务器证书+中间证书),不是 cert.pem。用openssl s_client看Verify return code,21 号错误就是缺链。 -
Let's Encrypt 续期失败:certbot 续期需要 80 端口可访问做验证。检查:安全组 80 是否放行、Nginx 的 80 跳转规则是否把
/.well-known/acme-challenge/也跳走了(要单独放行)。设个 cron/systemd timer 自动续期,别手动。 -
开了 TLS1.3 only 后老客户端连不上:某些老旧 Java 客户端(JDK 8 早期版本)、安卓 4.x 不支持 TLS 1.3。生产建议 TLS1.2+1.3 并存,除非确定客户端全可控。
面试常问
-
描述 TLS 握手过程? ① ClientHello:客户端发支持的 TLS 版本、加密套件列表、随机数。② ServerHello:服务器选定版本和套件,发证书链和随机数。③ 客户端验证证书(CA 签名、有效期、域名匹配),生成预主密钥,用服务器公钥加密发送(或走 ECDHE 交换)。④ 双方用三个随机数算出会话密钥,之后对称加密通信。TLS 1.3 把密钥交换合并进前两步,只需 1-RTT。
-
HTTPS 如何防中间人攻击? 靠 CA 体系:浏览器内置受信 CA 根证书,验证服务器证书链是否由可信 CA 签发、域名是否匹配、是否在有效期。中间人没有合法证书,伪造的证书验签不通过,浏览器报警。前提:用户不无视警告、CA 不被攻破或误签。
-
什么是网络分段,为什么重要? 把网络按信任级别划分成多个区域(公网/DMZ/应用区/数据区),区域间默认隔离、按需放行。重要性:限制攻击者横向移动——单点被攻破不扩散为全局失守。落地手段:VLAN、安全组、防火墙、bind 地址、零信任微分段。
自测三问
- TLS 握手中,非对称加密和对称加密各承担什么角色?为什么不全程用非对称?
- 本站的 MySQL 为什么应该
bind 127.0.0.1?如果必须远程访问,正确做法是什么? - HSTS 解决什么攻击?它和 301 跳转是什么关系?
小结一句话
网络安全的地基是三件事:TLS 保证传输机密完整、最小暴露面(bind+防火墙)保证不该通的不通、网络分段保证一次入侵不蔓延。
延伸
- Mozilla SSL Configuration Generator — 生成各版本 Nginx 的安全配置
- 下一课:密码学——对称/非对称、哈希、PKI,以及为什么密码必须用 bcrypt 而不是 SHA-256。