HTTPS 粗解:证书、TLS 握手与对后端配置的影响
1. HTTPS 在解决什么问题 ✉
在HTTP上,链路中的设备若能看到你的流量,就能读到 URL(含敏感 query)、Header、Body(含 token——敏感凭证更应放 Header 而非 query)。HTTPS = HTTP over TLS,TLS 主要负责三件事(和「机密性 / 完整性 / 身份」对应):
- 机密性:应用数据用协商出的对称密钥加密,窃听者拿到的是密文。
- 完整性:现代套件多用AEAD,篡改更容易被对端拒收或校验失败。
- 身份(对服务端而言最显眼):服务端用X.509 证书绑定域名(及公钥),客户端验证链与主机名后,才相信「当前连接对端是该域名背后的服务」。
还要知道两个「周边」问题:
混合内容(Mixed Content)
页面是 HTTPS,但页面里的脚本、接口、图片还用http://去拉,浏览器可能拦截或降级处理,表现为「线上环境只有部分资源失败」。前后端联调时,统一入口协议很关键。
HSTS(HTTP Strict Transport Security)
服务端通过响应头要求浏览器只走 HTTPS访问该域一段时间,可减少「首次仍打到 HTTP」被劫持的风险。配在云/WAF/Nginx/Spring 上都有可能;开发本地一般碰不到,上线要听安全/运维规范。
和 Java 后端的关系:你在内嵌 Tomcat 上开server.ssl.*,或前面用 Nginx/Ingress终结 TLS再回源 HTTP,都要理解证书与域名;Java 作为HTTPS 客户端(RestTemplate、RestClient、Feign、SDK)时还会单独碰到truststore与协议/套件不匹配问题。
2. 证书与证书链里有什么 📜
网站给你的材料一般是X.509。常见存放形态:
| 形态 | 说明 |
|---|---|
| PEM | Base64 +-----BEGIN CERTIFICATE-----头尾,可读文本,可链式拼接多个证书。 |
| DER | 二进制,扩展名不一定是.der,有时.crt既是 PEM 又是 DER,需用openssl x509 -in file -text -noout试读。 |
| PKCS#12(.p12 / .pfx) | 一个文件里常含私钥 + 证书链(+ 密码保护),Javakeytool、Springkey-store-type: PKCS12都爱用它。 |
2.1 字段里你最该盯的
| 概念 | 你需要知道的 |
|---|---|
| SAN(Subject Alternative Name) | 现代浏览器校验主名看 SAN;仅 CN(Common Name)不含 SAN 的老证书在部分环境会踩坑。你的访问域名/泛域名应出现在 SAN 列表里。 |
| Issuer | 颁发者:公共 CA、企业私有 CA、或自签名(Issuer = Subject)。 |
| 有效期 | notBefore~notAfter。过期必挂;建议设到期告警(提前 30 天很常见)。 |
| 公钥与用途 | 与服务器私钥成对;私钥绝不能进代码仓库。 |
| 证书链顺序 | 典型:站点证书→中间证书(可多层)→ 根证常已在客户端信任库。服务端配置时要避免「只发了站点证、没发中间证」。 |
2.2 DV / OV / EV(面试与采购常听)
- DV:验证域名控制权,签发快,浏览器地址栏一般只显示锁。
- OV / EV:多组织信息与更严审核,不能当作「绝对安全」,业务安全仍靠你自己(鉴权、防重放、日志等)。
2.3 公网 vs 内网
- 公网:Let’s Encrypt、DigiCert 等签的链,手机/浏览器/小程序默认可信。
- 内网/自签:客户端不信任 →
PKIX path building failed。正规做法是把企业根 CA 导入各环境的 truststore,或统一用私有 ACME/PKI;开发机临时导入要有限度。
吊销(CRL / OCSP)
浏览器与部分客户端会检查证书是否被吊销;排障时若遇「极少数客户端不行」,除链以外也可让运维侧看一眼吊销可达性(企业环境才多见)。
3. TLS 版本、套件与握手直觉 🔐
3.1 TLS 1.2 和 1.3 的差异(工程师版)
| 项 | TLS 1.2 | TLS 1.3 |
|---|---|---|
| 典型往返(RTT) | 完整握手常见2-RTT(外加 TCP) | 1-RTT完整握手更常见 |
| 套件 | 历史上套件繁杂,配置错误易出现弱套件 | 废弃大量旧套件,缺省更安全 |
| 中间人可见度 | 设计年代早,需更注意禁用旧协议 | 行业整体在推 1.3 |
实践:生产侧听运维/WAF 统一策略;Java 侧若指定过「只允许 TLSv1.2」之类 JVM 参数,升级时要和网关策略对齐,避免protocol_version/handshake_failure。
3.2 握手「剧情」仍是这几步(与 1.2 对齐理解)
- ClientHello:协议版本上限、随机数、支持的套件、SNI(你访问的域名)、扩展(ALPN 里可协商
h2/http/1.1等)。 - ServerHello + Certificate:服务端选定参数,下发证书链。
- 客户端验链、验主机名(证书是否覆盖 SNI 里的名字)。
- 密钥交换(ECDHE 等)→ 双方得到会话密钥。
- Finished后,应用数据(即HTTP)才加密传输——【001】里的报文在这之后才出现。
你要记的结论:业务 HTTP 报错与 TLS 报错层级不同;curl/浏览器若在握手阶段失败,根本不会到你的@GetMapping。
3.3 SNI(Server Name Indication)为何重要
同一台机、同一个公网 IP 上可能挂成千上万个 HTTPS 虚拟主机,靠的就是TLS 握手 ClientHello 里带的 SNI。老旧的 Java 8 早期或未正确配置 SNI 的客户端,访问多域名 HTTPS 时可能莫名其妙失败;你现在主力JDK 17一般问题不大,但排障时心里要有这根弦。
3.4 会话恢复(了解即可)
Session ID / Session Ticket让同客户端重复建连时少算密钥,体感主要是「复访更快」。深度排障很少从零扣这个,但若 CDN 文档让你开/关 ticket,知道它和「会话复用」有关即可。
4. Spring Boot 3.x 里开 HTTPS(内嵌容器)☕
很多团队不在 JVM 上终结 TLS,但若你需要「单机直出 HTTPS」或本地对齐生产,可以用下面几类配置。
4.1 PKCS#12(.p12 / .pfx)——最常见
server: port: 8443 ssl: enabled: true bundle: classpath:keystore.p12 key-store-password: ${SSL_KEYSTORE_PASSWORD} key-store-type: PKCS12 key-alias: myalias不同 Boot 小版本里key-store与bundle的键名请以你当前3.3.x 官方文档为准(有的示例仍写server.ssl.key-store)。要点不变:
- key-store-password:库口令;key-password若与库口令不同需单独指定。
- key-alias:一个 p12 里多个别名时必填对。
- 生产口令走环境变量 / Secret,勿提交 Git。
HTTP 与 HTTPS 同时开:可以用两套端口(例如 8080 HTTP + 8443 HTTPS),或只做反代层双协议;若只要「强制跳 HTTPS」,多在网关/Nginx做 301,比在业务里写过滤器更常见。
4.2 PEM(证书文件 + 私钥文件)
server: port: 8443 ssl: certificate: classpath:cert.pem certificate-private-key: classpath:key.pem注意:私钥文件权限在 Linux 上应收紧(仅运行用户可读)。链若分多段,按文档要求拼成服务端认可的fullchain形态。
4.3 协议与套件(进阶兜底)
若安全基线要求「仅 TLSv1.2+」或指定套件,常见是通过内嵌容器定制或反向代理统一策略。Boot 层若暴露server.ssl.enabled-protocols/ciphers(以文档为准),改前务必和运维对齐,避免老式客户端全挂。
4.4 Actuator 与健康检查
Kubernetes探针常打 HTTP。若你整站只开在 HTTPS,要么:
- 探针走独立 management 端口(仅内网),仍 HTTP;要么
- 探针路径在明文侧单独暴露(由平台规范约束)。
不要想当然把「业务 URL 必须 https」让探针也跟着复杂化,除非平台明确要求。
4.5 信任「反代传来的 https」——Forwarded 头
见下一节与5.3联动:X-Forwarded-Proto: https、X-Forwarded-Host等会影响Spring MVC 的绝对 URL 生成、Cookie 的 Secure、重定向 Location。Boot 3 常用:
server: forward-headers-strategy: framework或native——具体取值请对照当前 minor文档与你们网关实际发的头。没配时,常见 bug 是:登录后重定向成http://导致无限跳或 Cookie 丢失。
5. 反向代理终结 TLS(更常见的生产形态)📎
5.1 典型拓扑
浏览器 --HTTPS--> 云 LB / Nginx / Ingress --HTTP--> Spring Boot :8080 \--HTTPS mTLS--> (少数场景)TLS 在「边缘」终结,内网回源可走明文 HTTP(配合安全组/专线策略)。你的Java 日志里 RemoteAddr往往是反代 IP,真实客户端在X-Forwarded-For里(注意伪造链,信任边界以网关抹写为准)。
5.2 Nginx 配置加深一版
upstream spring_app { server 127.0.0.1:8080; keepalive 32; } server { listen 443 ssl; http2 on; server_name api.example.com; ssl_certificate /etc/nginx/certs/fullchain.pem; ssl_certificate_key /etc/nginx/certs/privkey.pem; # 按公司基线补 ssl_protocols / ssl_ciphers 等 location / { proxy_http_version 1.1; proxy_set_header Connection ""; proxy_pass http://spring_app; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; proxy_set_header X-Forwarded-Proto $scheme; proxy_set_header X-Forwarded-Host $host; proxy_set_header X-Forwarded-Port $server_port; proxy_connect_timeout 5s; proxy_send_timeout 60s; proxy_read_timeout 60s; } }fullchain.pem:站点 + 中间证书;别只拷「叶子证」。
WebSocket:若路径走 WS,要额外Upgrade、Connection相关头,或使用 Nginx/网关的websocket 专用示例。
5.3 Spring 侧为何要认 Forwarded
场景:用户访问的是https://api.example.com,网关回源http://127.0.0.1:8080。Spring 若不知道「外面是 https」,生成的redirect可能写成http://,OAuth2 回调、表单 action、HATEOAS等都会歪。配好forward-headers-strategy并只信任你家网关 IP写的头(防 Spoof),是生产必备意识。
5.4 云厂商四层/七层 LB
七层 LB 往往在控制台上传证书,回源可指定 HTTP;注意健康检查路径、会话保持与TLS 只在入口的组合。细节跟阿里云/腾讯云/AWS 控制台走,此处只强调:证书续期在 LB/CDN 控制台也可能发生,别忘了不仅 Nginx。
6. Java 作为客户端访问 HTTPS 上游 🎯
6.1 默认信任从哪来
JRE/JDK 带着lib/security/cacerts(密码默认changeit),里面是公共 CA 根。公网 HTTPS一般够用。
6.2keytool:看库、导入根证(示例)
# 列出 cacerts 里别名(路径随 JDK 安装变) keytool -list -keystore "%JAVA_HOME%\lib\security\cacerts" -storepass changeit企业根证书导入(示例,别名自定):
keytool -importcert -noprompt -alias corp-root -file corp-root.pem -keystore "%JAVA_HOME%\lib\security\cacerts" -storepass changeit注意:改系统cacerts影响整台机所有 Java 进程,很多公司改为启动时挂自定义 truststore,或容器镜像层统一管理。
自定义 truststore 启动:
-Djavax.net.ssl.trustStore=E:/dev/corp-truststore.p12 -Djavax.net.ssl.trustStorePassword=yourSecret -Djavax.net.ssl.trustStoreType=PKCS126.3 典型异常与含义
| 异常 / 日志关键字 | 更可能原因 |
|---|---|
PKIX path building failed | 不信任颁发链、缺根/中间、或连错环境(测网证书打生产域名) |
CertificateExpiredException | 证书过期 |
CertificateNotYetValidException | 本机时间不对或证书未生效 |
No subject alternative DNS name matching | 访问的 Host 不在证书 SAN |
javax.net.ssl.SSLHandshakeException | 协议/套件/证书错误的大箩筐,需打开-Djavax.net.debug=ssl:handshake(仅排障短暂开) |
SunCertPathBuilderException | 常与 PKIX 同类,链构造失败 |
6.4 框架层:RestTemplate / RestClient / Feign
- 公网 CA:一般零配置。
- 私有 CA:运维发根证→ 进 truststore(推荐)→ 再谈代码。
- 「开发环境跳过校验」类
TrustManager:不要复制进生产;代码评审应拦。
Feign / HttpClient 若单独配了 SSL 上下文,要确认没有覆盖默认的 SNI 行为(少数老配置会踩坑)。
7. 联调与排障:命令、流程与表 ✅
7.1 curl
看握手与证书(Windows 可把管道后换成findstr):
curl -v "https://api.example.com/actuator/health" 2>&1 | findstr /i "subject issuer expire SSL certificate"指定解析(排除 DNS 捣乱):
curl -v --resolve api.example.com:443:203.0.113.10 "https://api.example.com/"7.2 openssl s_client(有 OpenSSL 的环境)
openssl s_client -connect api.example.com:443 -servername api.example.com -showcerts </dev/null关注:证书链有几张、Verify return code是否 0、subject=CN/ SAN、OCSP相关输出(视版本)。
7.3 浏览器
Chrome:锁图标 → 连接是安全的 → 证书有效,看颁发者、有效期、使用者可选名称。
Safari / Firefox 路径略不同,原则一样:链、日期、域名。
7.4 排障流程(可当 check list)
- 先分清:是握手失败还是HTTP 4xx/5xx(握手失败时很多监控探不到业务指标)。
curl -v/openssl s_client在出问题的同一网络环境跑(公司出口、用户家宽、机房内网)。- 核对访问的 Host与证书SAN;核对系统时间。
- 服务器/Nginx 侧:
fullchain是否真 full;是否改过中间证未重启。 - Java 客户端:truststore是否包含企业根;JVMTLS 版本是否过严或过旧。
- 仅部分移动网络失败:想到IPv6、SNI、过时安卓 WebView、中间设备劫持等(需要抓包与运营侧信息)。
7.5 浏览器错误与可能原因(扩表)
| 现象 | 可能原因 |
|---|---|
NET::ERR_CERT_DATE_INVALID | 过期/未生效/本机时间错误 |
NET::ERR_CERT_COMMON_NAME_INVALID | 域名与 SAN 不匹配 |
ERR_CERT_AUTHORITY_INVALID | 自签或私有 CA 未被信任 |
NET::ERR_SSL_VERSION_OR_CIPHER_MISMATCH | 协议/套件过新或过旧 |
| 小程序合法域名失败 | 非 443 默认、TLS 不合规、证书链问题、域名未登记、业务未备案(政策以平台文档为准) |
8. 综合示例:本地证书与 Spring Boot 💻
8.1 最简自签(仅本机,带 SAN 更好)
/etc/hosts或 Windowshosts把local.test指到127.0.0.1,用 SAN 覆盖该名字(OpenSSL 命令各版本用req v3_req+subjectAltName配置块;此处给思路,命令以你本机 OpenSSL 手册为准):
- 生成私钥+CSR+自签,SAN 含
DNS:local.test、DNS:localhost。 - Spring:
server.ssl.certificate/certificate-private-key指向生成文件,server.port=8443。 - 浏览器首次仍会红屏——导入自签为受信任根或每次点高级继续,团队规范通常禁止把这种证书用于联调小程序。
8.2 开发利器 mkcert(可选)
mkcert在开发机上生成本地受信 CA,浏览器少红屏;仍非公网方案,上线与小程序无关。
8.3 「与生产一致」的路径
尽量用测试环境已申请的正式域+正式证书(或 staging),连接到类生产网关,减少「本地自签一切正常、上云全挂」的落差。
9. 双向 TLS(mTLS)——知道边界即可 🔗
部分开放银行 / 企业 B2B要求客户端也交证书,服务端校验「你是谁」。此时除服务端server.ssl.*(trust-store 配合作方根或伙伴客户端认证配置)外,客户端要配keyStore(自己的证书+私钥)。细节强烈依赖对方接口文档与你们网关是否 offload,这里只提醒:遇到「要交客户端证书」的需求,别在业务代码里硬编码私钥路径密码,交给配置中心与密钥托管。
10. 小程序 / 开放平台的「合法域名」——和 HTTPS 的关系 📱
不同平台细则会变,开发时可按下面自查(以微信等官方文档为准,这里列工程常识):
- 接口域名必须是HTTPS(小程序里
request合法域名列表)。 - 证书链完整、TLS 版本与套件满足平台最低要求(旧版文档常写不支持 SSLv3 等,需读当期说明)。
- 域名已加到控制台「合法域名」,且与证书 SAN一致。
- 部分能力要求ICP 备案、业务域名校验文件等——与纯 TLS 不同,但排障时一起查。
「后端同事说接口OK,小程序不通」——先手机浏览器打开同一 URL 看证书;再用开发者工具 Network;最后核对控制后台域名配置。
11. OpenSSL:带 SAN 的自签与 CSR 思路(可复制骨架)🧰
下面是一段「思路骨架」:用独立配置文件把SAN写清楚,避免证书只有CN=localhost、没有 SAN,被 Chrome 等直接标红。具体命令以你机器上OpenSSL 3.x / 1.1.1文档为准,路径自行替换。
第一步:写openssl-san.cnf(示例)
[ req ] default_bits = 2048 prompt = no default_md = sha256 distinguished_name = dn req_extensions = v3_req [ dn ] CN = local.test [ v3_req ] subjectAltName = @alt_names [ alt_names ] DNS.1 = local.test DNS.2 = localhost IP.1 = 127.0.0.1第二步:生成私钥 + CSR + 自签证书(示例命令序列)
openssl req -new -x509 -nodes -days 365 -keyout local.test.key -out local.test.crt -config openssl-san.cnf -extensions v3_req生成的local.test.crt+local.test.key可交给 Spring Bootserver.ssl.certificate/certificate-private-key(或先拼成 p12 再用key-store)。记得在hosts里把local.test指到本机,否则浏览器用别的名字访问仍会报域名不匹配。
验证书内容(看 SAN 是否在):
openssl x509 -in local.test.crt -noout -text | findstr /i "Subject Alternative Name DNS"Linux / macOS 把findstr换成grep -A1 'Subject Alternative Name'即可。
12. Let’s Encrypt / 自动化续期(工程侧常识)📅
公网免费证书里ACME(如 Let’s Encrypt)最普及。工程上你要记住:
- 有效期短(常见 90 天),必须自动续期(certbot、caddy、云厂商托管证书等),不能靠人肉日历。
- 验证域名所有权常见HTTP-01(临时文件或路由)、DNS-01(改 TXT 记录)。在 K8s/多副本上要用平台认可的 controller,避免验证打到错误 Pod。
- 证书更新后,Nginx 要 reload、部分 Java 进程要热加载或滚动发布——别把「续期成功」当成「应用已用上新证」。
- 完整链:ACME 客户端一般会给你
fullchain.pem,部署时用它,而不是只拿单张cert.pem。
你只写 Java 时,续期多在运维;但事故复盘里「昨晚续期、今早握手失败」要能参与对话。
13. Nginx 上再多了几招(安全 баз线 + mTLS 提示)🛡
13.1 安全基线(示意,具体以你们等保/基线为准)
ssl_protocols TLSv1.2 TLSv1.3; ssl_prefer_server_ciphers off; # 若仍要 TLSv1.2,可配 ssl_ciphers ... 由安全组给清单;TLSv1.3 下多由 OpenSSL 默认择优OCSP Stapling(减少客户端延迟、隐私略好)在 Nginx 有ssl_stapling on;等指令,但要链与解析都正确,配不好不如关——交给运维评估。
HSTS(示意):
add_header Strict-Transport-Security "max-age=31536000; includeSubDomains" always;首次上线要确认没有依赖明文 HTTP 的用户书签,否则最长一年内难撤回影响;includeSubDomains影响子域,慎开。
13.2 mTLS 在 Nginx 边缘做校验(概念)
ssl_client_certificate /etc/nginx/certs/ca-chain.pem; ssl_verify_client optional; # 或 require,视业务通过后再把必要信息proxy_set_header带给后端(如证书序列号需在网关文档化,别直接传整 PEM 进 HTTP 头)。Spring 若不做 mTLS,就信任已鉴权的内网回源。
14. Spring Bootserver.ssl与转发:属性速查(3.3 语境)☕
下表是「认得名字、会搜文档」用,精确键名以官方文档为准(Minor 升级会补属性)。
| 场景 | 常见配置方向 |
|---|---|
| PKCS12 路径 | server.ssl.key-store(或文档中的 bundle 写法) |
| 密钥库密码 | server.ssl.key-store-password(环境变量注入) |
| 密钥口令不同 | server.ssl.key-password |
| PEM 证书 | server.ssl.certificate(或certificate-chain) |
| PEM 私钥 | server.ssl.certificate-private-key |
| 禁用旧协议 | server.ssl.enabled-protocols |
| 限制套件 | server.ssl.ciphers |
| 只信任客户端证 | server.ssl.client-auth(如需 mTLS) |
| 识别反代 HTTPS | server.forward-headers-strategy=framework等 |
双端口:有人喜欢8080仅内网、对外只暴露8443—— 安全组与防火墙要和架构一致,别只留下 8080 给公网。
15. JVM 与 Java 出站的调试开关(仅排障短暂开)🔧
握手调试(日志极多,仅本机或灰度短时间):
-Djavax.net.debug=ssl:handshake:verbose你会看到ClientHello、选中套件、证书链加载等;抓完记得关,避免磁盘爆掉。
信任库 / 密钥库:
-Djavax.net.ssl.trustStore=C:\dev\corp-truststore.p12 -Djavax.net.ssl.trustStorePassword=secret -Djavax.net.ssl.trustStoreType=PKCS12误区别踩:
- 把
trustStore和keyStore(客户端证书)混在一个文件里管理,权限难审计。 - 生产关闭证书校验(
TrustAll)方便「联调」,结果忘了关上线。 - 只改 Nginx 证书却未reload,导致新旧证并存、节点不一致。
16. 和【001】叠在一起:分层心智图 📌
以后排障可以在心里按层掰:
[ 浏览器 / 小程序 / Postman ] │ DNS(【003】会讲) ▼ [ TCP 连接 ] │ TLS 握手(本篇) ▼ [ HTTP/1.1 或 HTTP/2 上的请求/响应 ] ← 【001】 ▼