DNS、TLS 与 HTTPS:从找到地址到证明服务身份
“域名能解析”与“访问的是正确服务”之间隔着两条独立的证明链。DNS 返回候选地址,解决的是路由入口;TLS 验证证书链与主机名,解决的是通信对端身份;HTTP 才开始交换业务语义。若把三者压缩成一次 https:// 调用,线上会出现很难解释的现象:DNS 已切流但旧地址仍被使用,IP 连通却报证书错误,SNI 命中了错误虚拟主机,TLS 成功却协商成了意外协议。
DNS 成功不能替代 TLS 身份验证;TLS 握手成功也不能替代 HTTP 授权。每一层只能证明自己负责的事实。
DNS 返回的是一组带时效的候选事实
应用通常先询问本机 stub resolver,本机再通过递归解析器寻找答案。递归解析器可能命中缓存,也可能从根、顶级域和权威服务器逐级获得委派与资源记录。RFC 1034 与 RFC 1035 奠定了这套分层名称系统。最终结果不是永久映射,而是带 TTL 的记录集。
缓存不只存在于递归解析器。操作系统、JVM、HTTP 客户端、连接池乃至服务发现侧车都可能保留结果或已建立连接。因此一次 DNS 变更的生效时间不是权威 TTL 本身,而是所有缓存与连接生命周期的最大影响。回滚也受同样约束。切流前先降低 TTL,只能影响尚未缓存的后续查询,不能让旧连接瞬间消失。
不存在的名称也可以被负缓存。故障恢复时若只盯着正向记录 TTL,会困惑于“记录已经加回,部分机器仍报不存在”。排查需要记录查询名、记录类型、响应码、答案、解析器地址、TTL、是否命中缓存和耗时,而不是只打一个 UnknownHostException。
CNAME 提供别名链,但链过长会增加查询往返与故障面;A 和 AAAA 提供 IPv4/IPv6 候选;RFC 9460 定义的 SVCB/HTTPS 记录还能在连接前表达替代端点、端口和应用协议提示。它们优化发现,却不改变身份规则:连接到替代地址后,证书仍须对原始服务名有效。
双栈不是“先查到谁就永远用谁”
只串行尝试 AAAA 再等待长超时后尝试 A,会让一条损坏的 IPv6 路径拖慢所有请求;完全偏向 IPv4 又会掩盖 IPv6 的真实可用性。RFC 8305 的 Happy Eyeballs 思路是取得多地址族候选后,以小幅错开的受控竞速降低单一路径失败的尾延迟,同时保留地址族偏好和历史反馈。
这不是无限并发拨号。连接器要限制同时尝试数,成功一个后取消其余尝试,并把 DNS、每个地址的 connect、TLS 和整体调用都纳入同一个绝对 deadline。若每个候选地址都重新获得完整 3 秒超时,四个地址就能把用户的 3 秒目标膨胀到 12 秒。
examples/backend-development/network-web/dns-tls-https/DnsResolutionDemo.java 枚举 localhost 的地址族,并验证结果至少包含回环地址;地址数量和 IPv4/IPv6 组合受本机 hosts、解析器和网络栈影响,不能把某台机器的数量当成协议不变量。严格编译和运行:
javac --release 17 -Xlint:all -Werror examples/backend-development/network-web/dns-tls-https/DnsResolutionDemo.java examples/backend-development/network-web/dns-tls-https/TlsIdentityPolicyDemo.java
java -cp examples/backend-development/network-web/dns-tls-https DnsResolutionDemo
java -cp examples/backend-development/network-web/dns-tls-https TlsIdentityPolicyDemo一次运行结果是:
resolved=true loopbackOnly=true hasIpv4=true hasIpv6=true count=2
endpointIdentification=HTTPS sni=api.example.com insecurePolicyRejected=true真正稳定的断言是 resolved 与 loopbackOnly,而不是 count=2。这个区别正是可移植实验与环境快照的区别。
TLS 1.3 在握手中完成什么
TLS 同时提供机密性、完整性和对端认证。RFC 8446 中,客户端通过 ClientHello 提供支持的版本、密码套件、密钥份额、SNI 和 ALPN 等信息;服务端选择参数、返回证书与证明材料,双方导出握手密钥和应用流量密钥。TLS 1.3 移除了多种遗留算法与握手分支,但部署中的证书、信任库、时钟、SNI 和协议协商仍会制造大量失败。
SNI 让同一 IP 上的 TLS 终止点在握手阶段选择证书和虚拟主机;它是路由提示,不是身份验证。ALPN 让双方选择 http/1.1、h2 或 h3 等应用协议;它也不是授权。真正的服务端身份验证至少包含四步:从叶子证书构造到受信任根的有效链;验证证书有效期与关键用途;把请求主机名与证书 SAN 匹配;确认算法和策略可接受。
直接用 IP 建连但请求域名服务时,仍应把原始域名用于 SNI 和主机名验证。为了“临时解决证书错误”而安装信任所有证书的 TrustManager、关闭 endpoint identification 或跳过 hostname verifier,相当于只保留加密而移除身份,主动中间人便可用自己的证书终止连接。
TlsIdentityPolicyDemo.java 不访问公网,而是检查一个 HTTPS 客户端策略是否同时设置 HTTPS endpoint identification 和 api.example.com SNI,并拒绝空身份策略。这段实验的意义不是复刻完整握手,而是把最容易在封装层丢失的安全不变量变成可测试配置。
证书链正确仍可能失败
证书故障必须按验证阶段分类。unknown_ca 指向信任链;certificate_expired 指向有效期或时钟;bad_certificate 可能来自客户端证书;hostname mismatch 指向 SAN 与访问名;handshake_failure 还可能是版本、算法、客户端认证或服务端策略没有交集。把所有异常包装为“SSL error”会让值班人员只能靠猜。
证书轮换应允许新旧信任重叠。若先让服务端切到新签发链,再更新仍固定旧根的客户端,结果就是全量握手失败。更稳健的次序是先让验证方信任新链,观察覆盖,再切换服务端证书,最后移除旧信任。私有 PKI 还要明确吊销策略:OCSP/CRL 不可达时是硬失败还是软失败,这是一项安全与可用性取舍,不能交给库默认值偶然决定。
会话恢复能减少握手 CPU 与往返,但恢复票据是敏感凭据,需要轮换和作用域控制。TLS 1.3 的 0-RTT 提前数据可被重放,只适合服务端明确判定可重放的操作;“请求使用 HTTPS”并不能自动让非幂等写入适合 0-RTT。
HTTPS 终止位置决定信任边界
TLS 在负载均衡器终止后,客户端身份、原始 scheme 和主机信息往往以转发 Header 继续传递。应用只能信任由明确代理地址注入、且入口已清洗同名外部 Header 的元数据。否则攻击者可以自行提交 X-Forwarded-Proto: https 绕过安全跳转,或伪造源 IP 绕过访问控制。
端到端 TLS、边缘终止后内网明文、边缘与后端再次 TLS 各有成本。判断依据包括威胁模型、合规要求、服务身份、证书自动化、观测能力和故障域,而不是“内网天然可信”。双向 TLS 能认证客户端工作负载身份,但它不替代业务授权;一个持有合法证书的服务仍可能无权读取某个租户数据。
把解析、连接、握手拆成独立指标
一条 HTTPS 调用至少应拆出 DNS 时间、候选地址数、选中的地址族、TCP connect 时间、TLS handshake 时间、TLS 版本、密码套件、ALPN、证书到期余量、连接复用情况和首字节时间。高 p99 只写成“HTTP 慢”时,DNS 缓存失效、IPv6 黑洞、证书链过大、TLS 终止点 CPU 饱和都会混在一个桶里。
故障证据也必须保留原始层次:DNS 响应码与 TTL;每个地址的连接错误;TLS alert 和验证异常;最终 HTTP 状态。敏感字段不要直接进入日志:证书序列号可记录,私钥、会话票据、Cookie 和 Authorization 不可记录。抓包在 TLS 之后看不到应用明文是安全属性,不是诊断缺陷;需要通过握手元数据、服务端日志与 trace 关联补足。
正确的推理顺序是:名称是否得到仍有效的候选端点;路径是否在 deadline 内建立;TLS 是否证明了预期名称的身份并协商出预期协议;HTTP 才是否完成请求。每一问都有独立证据,也有独立的超时、缓存和安全边界。
解析一致性与流量切换
权威服务器返回多条地址时,记录顺序不一定就是全局负载算法。递归解析器可能轮换答案,客户端也可能重排、缓存或基于历史连接质量选择。若希望精确控制权重、健康和地域,必须理解 DNS 只在查询时给出候选,无法撤销已经建立的连接。紧急摘除一个地址后,还要在负载均衡层停止接收新连接、对旧连接执行优雅下线,并等待各层缓存与连接年龄收敛。
“污染”也要分层。权威数据错误、递归缓存陈旧、本机 hosts 覆盖、搜索域补全和应用自带缓存会产生相似症状。诊断应分别对系统解析器、指定递归服务器和权威服务器查询,并比较完整名称、记录类型、响应码、TTL 与权威标志。直接把生产代码改成固定公共 DNS,可能绕过企业分流、私有域和合规边界。
证书身份与服务授权是两次判断
证书 SAN 匹配 api.example.com,只证明持有相应私钥且链被客户端信任的端点;它不证明该端点有权访问租户 A,也不证明返回内容符合业务规则。mTLS 中客户端证书同理:TLS 层得到一个工作负载身份后,应用仍要把身份映射到主体、权限和租户,并处理证书轮换期间的一对多映射。
通配符证书扩大了单个私钥泄露的影响面,且匹配规则不是任意多级后缀。内部服务为方便而共享一个 *.example.com 证书,会把原本隔离的服务身份绑定在同一密钥和签发流程上。更细粒度的服务证书配合自动签发、短有效期和密钥不可导出,通常比依赖人工长期通配符更容易限制爆炸半径。
握手容量也需要背压
TLS 握手涉及非对称密码、证书链解析和状态分配,攻击者可以用大量未完成握手消耗 CPU 与内存。终止点需要限制新建连接速率、并发握手、ClientHello 大小、证书链大小和握手超时;对会话恢复命中率与全握手率分开监控。只限制 HTTP QPS 时,攻击流量可能在进入 HTTP 之前已经耗尽终止点。
证书到期监控不能只扫描配置仓库,要从真实入口完成握手,覆盖 SNI 路由、CDN、区域和备用证书链。告警应基于剩余有效期、握手成功率和新旧链分布,并验证自动续签之后新证书确实被所有终止点加载,而不是仅确认文件已生成。
