第 11 章 认证、授权与传输安全:到底哪一层在保护你

你的数据,到底在哪一层被保护?答案可能让你意外——很多时候,你以为在保护你的那层,其实根本没在保护你。


11.0 引子:内网接口裸奔,和一次中间人

老张:老哥,安全部门扫描,说你那个内网接口是明文 HTTP,Authorization 头里那个 token 是 base64,一抓包就全看到了。

你:内网还要加密?而且 token 不是 base64 过了吗?

老张:base64 是编码,不是加密。谁都能解码。

墨叔:又来了。两个误区:①”内网不需要安全”;②”base64 过就安全了”。

今天这一章,我们把”认证、授权、加密”这几件事彻底讲清楚——尤其是那个反直觉的结论:你以为在保护你的那层,可能根本没用。


11.1 先把问题问对:认证、授权、加密是三件不同的事

墨叔:先拆三个经常被混为一谈的词。

概念回答的问题例子
认证(Authentication)“你是谁?“登录、出示身份证
授权(Authorization)“你能做什么?“你是管理员,能删帖子
加密(Encryption)“内容会不会被别人看到?“HTTPS 加密传输

你:这三个词我经常混。

墨叔:混了就会出大事。经典错误:“我认证过了(知道你是谁),就等于授权你(什么都能做)“——这是无数越权漏洞的根源。

更常见的混淆是认证 vs 加密:Authorization 头里放 token,只解决了”认证”(证明你是谁),没有解决”加密”(内容裸奔)。引子里老哥的问题就是这个——他以为 token 用 base64 处理过,就”安全”了,但实际上:

base64 是”编码”(把二进制变成可打印字符),不是”加密”(把明文变成密文)。编码任何人可逆,加密只有持钥者可逆。


11.2 四层安全模型:你的数据在每一层分别被谁保护

墨叔:HTTP 的安全,是一个分层的问题。你的数据,在不同层被不同机制保护。这张图是你的安全地图:

┌─────────────────────────────────────────┐
│ 应用层:业务自己的权限、字段级控制        │ ← 业务逻辑保护
├─────────────────────────────────────────┤
│ HTTP 认证层:WWW-Authenticate / Bearer   │ ← 认证方案保护
├─────────────────────────────────────────┤
│ 传输层:TLS(HTTPS)                      │ ← 加密+完整性+身份保护
├─────────────────────────────────────────┤
│ 网络层:IP 白名单、防火墙、内网隔离        │ ← 网络边界保护
└─────────────────────────────────────────┘

你:所以”安全”是每一层各管一段?

墨叔:对,而且每一层只能管自己那一段,替代不了别的层:

  • 网络层(内网/防火墙):只能挡住”网络外的人”,挡不住”已经进了内网的人”(内鬼、被攻陷的服务器、横向渗透)。这就是”内网也要加密”的原因。
  • 传输层(TLS):保护”传输过程中”不被窃听篡改,保护不了”服务端本身被攻破”,也不防”你连的就是一个骗子”(这个后面讲)。
  • HTTP 认证层:证明”你是谁”,但不加密内容(所以 Basic 认证要配合 HTTPS)。
  • 应用层:最后的防线,业务逻辑自己的校验。

记住这个分层:“安全”不是一层的事,任何一层说”我负责安全”,都是谎言。真正的安全,是每一层都做好自己那一段。


11.3 HTTP 认证框架:challenge-response 的设计

墨叔:HTTP 的认证,有一个框架性的设计——challenge-response(挑战-应答)。

你:challenge-response?

墨叔:看这个流程。当你访问一个需要认证的资源:

客户端                                  服务器
  |--- GET /admin -------------------->|
  |<-- 401 Unauthorized ---------------|
  |    WWW-Authenticate: Basic realm="admin area"   <- 服务器"挑战":请用 Basic 方式认证
  |                                   |
  |--- GET /admin -------------------->|
  |    Authorization: Basic dXNlcjpwYXNz   <- 客户端"应答":这是我的凭证
  |                                   |
  |<-- 200 OK(认证通过)---------------|

你:所以 401 + WWW-Authenticate 是”挑战”,Authorization 是”应答”。

墨叔:对。这个框架的精妙在于它的可扩展性:

  • WWW-Authenticate 头里声明了认证方案(Basic、Digest、Bearer…),告诉客户端”这次该用哪种方式”。
  • 客户端用对应的 Authorization 头回应。
  • 未来要加新的认证方案,只需定义一个新的方案名,不用改协议。

这就是第 5 章”首部作为扩展插槽”在安全领域的又一次体现——认证方案本身也是可扩展的。


11.4 Basic 与 Digest:两个认证方案,一个活着,一个死了

Basic:最简单,也最”裸”

Basic 认证就是 base64(用户名:密码):

Authorization: Basic dXNlcjpwYXNz
              (dXNlcjpwYXNz 解码后就是 "user:pass")

你:这不就等于明文吗?base64 一下而已。

墨叔:对。Basic 认证本身不提供任何加密。所以它必须跑在 HTTPS 上,否则用户名密码就是”base64 编码的明文”,任何抓包的人都能解码。

那 Basic 为什么还存在? 因为它简单到极致:实现成本几乎为零,适合内部系统、适合配合 HTTPS 做最简单的认证。“简单 + HTTPS 兜底”就是它的生存之道。

Digest:一个”更安全”的方案,为什么死了?

墨叔:这里有个很有意思的故事,值得你仔细听。

Digest 认证(RFC 2617)设计的初衷,是解决 Basic 的”明文问题”:它不直接发密码,而是发密码的哈希(配合 nonce 随机数、MD5 摘要),这样即使被抓包,攻击者也拿不到明文密码。

听起来比 Basic 安全多了,对吧?但它几乎没人用,最后死了。

你:为什么?技术上它明明更好啊。

墨叔:因为它”好”得很尴尬,被一个更通用的方案淘汰了。我们拆解它的死因:

死因一:它只防住了最弱的一种攻击。 Digest 防的是”被动窃听”(抓包看明文密码)。但它不防:

  • 主动中间人:攻击者可以在中间”冒充服务器”,让你用 Digest 跟他认证,他再把你的请求转发给真服务器。Digest 对这个无能为力。
  • 服务端数据库被拖库:Digest 需要服务器存”密码的哈希(HA1)“,拖库后攻击者照样能离线暴力破解。

死因二:它只保护”认证”这一件事,而 TLS 保护”一切”。 这是最关键的。Digest 费尽心机只保护了”密码不被明文看到”。而 HTTPS(TLS)一旦普及,它把整个报文(包括密码、包括所有数据)都加密了。Digest 那点”保护密码”的价值,被 TLS 完整覆盖了。

你:所以是”一个专门的功能,被一个通用的方案降维打击了”?

墨叔:精确。这是技术演进里一个非常重要的规律:

一个”专门解决某个具体问题”的方案,往往会被一个”顺便也能解决这个问题”的通用方案淘汰。 因为通用方案覆盖的场景更广、生态更强、边际成本更低。

Digest 只解决”密码传输”,TLS 解决”所有数据传输”,顺带也解决了”密码传输”。当 TLS 普及后,Digest 的存在意义就消失了。

死因三:复杂,且没人维护。 Digest 涉及 nonce、qop、MD5、摘要算法协商……复杂且各实现参差,而一个”不流行”的标准,会越来越不流行(没人实现、没人维护、没人敢用)。

你:所以 Digest 的死,不是因为它错,而是因为它”不够通用”?

墨叔:对。记住这个教训,它会改变你判断技术的方式:判断一个技术会不会被淘汰,不只看它”好不好”,更要看它解决的是不是一个”正在被通用方案接管”的问题。


11.5 Bearer:现代事实标准

墨叔:现在最主流的方案是 Bearer(RFC 6750),也就是我们天天用的:

Authorization: Bearer <token>

你:Bearer 和 Basic/Digest 的本质区别是什么?

墨叔:Basic/Digest 是**“凭密码”(每次证明身份),Bearer 是”凭令牌”**(出示一个已经签发的令牌)。

  • Bearer 的语义很直白:“持有(bear)这个 token 的人,就被视为 token 代表的身份。”
  • 它不规定 token 怎么生成(可以是 JWT、可以是随机字符串、可以是 opaque token),只规定”怎么携带”(放 Authorization: Bearer 头里)。

你:所以 Bearer 只是一个”携带方式”的标准,token 本身是什么,它不管?

墨叔:对。这也是它成功的原因——它足够”薄”,只标准化了最必要的那一点(放哪个头、什么格式),把”token 怎么生成、怎么验证”的复杂性留给了应用。和第 9 章”无状态下放复杂性”是同一个哲学。


11.6 TLS/HTTPS:它到底防什么,不防什么

墨叔:现在讲传输安全的基石——TLS。这是最容易产生”虚假安全感”的地方。

TLS 防的三件事:

防护含义
加密(Confidentiality)内容不被窃听
完整性(Integrity)内容不被篡改
身份认证(Authentication)服务器(和可选客户端)的身份是真的

TLS 不防的三件事(这才是重点):

1. 不防”你连的就是个骗子”。 HTTPS 认证的是”你连的这个服务器,确实拥有这个域名的证书”。但它不防”你主动连了一个假域名”。比如你访问 g00gle.com(注意是 0 不是 o),它也可以有合法的 HTTPS 证书,浏览器显示”安全”,但它就是个钓鱼站。

2. 不防”服务端自己被攻破”。 TLS 保护”传输过程”,但数据到了服务器,服务器自己如果被攻破(拖库、内存泄露),TLS 毫无办法。

3. 不防”中间人代理”(企业环境)。 公司为了安全审计,会部署 MITM 代理——它在你电脑上装一个受信任的根证书,然后解密你的 HTTPS 流量、检查、再加密转发。从你的浏览器看,一切正常(小绿锁),但你的”加密”在代理那里是被解密的。

你:所以 HTTPS 不是”绝对安全”,是”在传输这一层安全”?

墨叔:对。这又回到了 11.2 节的分层:TLS 只保护”传输层”,它替代不了应用层、网络层、以及”你自己别连钓鱼站”的警惕。


11.7 降级攻击与 HSTS

墨叔:讲一个 HTTPS 的经典威胁——降级攻击(Downgrade Attack),以及它的解法 HSTS。

场景:用户访问 https://bank.com。但攻击者(中间人)拦截请求,把它”降级”成 http://bank.com(明文)。因为 HTTP 没有加密,攻击者就能窃听/篡改。

你:怎么防?

墨叔:两个层次:

第一层:HTTP 重定向。 服务器在 http:// 上返回 301 重定向到 https://。但这个 301 本身是明文传输的,攻击者可以在你收到 301 之前就拦截篡改。所以这个不可靠。

第二层:HSTS(HTTP Strict Transport Security)。

服务器在 HTTPS 响应里加一个头:

Strict-Transport-Security: max-age=31536000; includeSubDomains; preload

意思是:“浏览器,记住:这个域名,未来 1 年内只能用 HTTPS 访问,绝不尝试 HTTP。”

你:所以 HSTS 是”浏览器记住的强制 HTTPS 承诺”?

墨叔:对。它的关键是**“一旦记住,就不再给降级的机会”**——浏览器下次直接连 HTTPS,根本不会发那个”可被篡改的 HTTP 请求”。

但 HSTS 有个”第一次访问”的鸡生蛋问题:如果用户第一次访问是 HTTP(还没收到过 HSTS 头),攻击者仍然可以降级。解法是 HSTS Preload List(预加载列表)——Google 维护一个列表,浏览器内置了”这些域名永远 HTTPS”,从第一次访问就生效。


11.8 证书、CT 与证书固定的兴衰

墨叔:最后快速过几个证书相关的概念,它们也是面试高频:

证书链

你的证书不是孤立的,它是一条链:

根证书(Root CA,内置在操作系统/浏览器里)
  └── 中间证书(Intermediate CA)
        └── 你的证书(Leaf,绑定了你的域名)

浏览器验证时,从叶子一路验证到根,每一环都信任,才算有效。

证书透明度 CT(Certificate Transparency)

问题:如果某个 CA 被攻破,攻击者能签发一个假证书冒充你的网站,浏览器却验不出(因为证书链是合法的)。

CT 的解法:所有 CA 签发的证书,必须公开记录在一个公开的、不可篡改的日志里。这样,如果你的域名被”偷偷”签了一个假证书,你(或任何人)能在公开日志里发现它。CT 把”签发证书”这个原本黑箱的操作,变成了”可审计的公开记录”。

证书固定(Certificate Pinning)的兴衰

证书固定:客户端硬编码”我只信任这个特定的证书/公钥”,别的都不认。

它曾经很流行,但现在衰落了,原因:

  1. 太脆:证书会轮换、会过期,一换证书,客户端就得跟着更新,否则全挂。
  2. 容易被滥用:配置错了,直接把自己锁死(用户全部连不上)。
  3. CT + 更强的 CA 审计 已经提供了大部分”防假证书”的能力,pinning 的边际价值下降。

你:所以又是一个”专门方案被通用方案淘汰”的例子?

墨叔:对。你已经在 11.4 节 Digest 那里见过这个规律了。技术判断的核心,就是识别”这个方案解决的是不是正在被通用方案接管的问题”。


11.9 Go 落地:TLS 配置的几个要点

墨叔:Go 里配 HTTPS,几个要点:

server := &http.Server{
    Addr: ":443",
    TLSConfig: &tls.Config{
        MinVersion: tls.VersionTLS12,   // 至少 TLS 1.2,禁用老版本
        // 千万别设 InsecureSkipVerify: true !这是生产环境大忌
    },
}
server.ListenAndServeTLS("cert.pem", "key.pem")
 
// 作为客户端时,默认会验证证书。千万别为了省事关掉验证:
// client := &http.Client{Transport: &http.Transport{
//     TLSClientConfig: &tls.Config{InsecureSkipVerify: true},  // 危险!
// }}

你:InsecureSkipVerify 是干嘛的?为什么危险?

墨叔:它让客户端跳过证书验证——也就是”不管对面是不是真的、有没有合法证书,都连”。这等于亲手拆掉了 HTTPS 的”身份认证”能力,只剩下加密(还防不了主动中间人)。

很多人为了”本地调通”设了 InsecureSkipVerify: true,然后忘了改回来,带着它上了生产**。结果:你的服务信任了任何”自称是对方”的人,中间人攻击大门敞开。这是 Go 里最常见的安全事故之一。


11.10 排查清单:安全类问题

第 1 步:到底哪一层在保护我?
        → 网络层?TLS?认证?应用层?别指望错了层

第 2 步:认证和加密分开确认了吗?
        → 有 token(认证)≠ 有加密(传输)

第 3 步:TLS 配置最低版本、证书有效期?
        → 老 TLS 版本有已知漏洞

第 4 步:有没有 InsecureSkipVerify / 关闭证书验证?
        → 有就是事故

第 5 步:HSTS 配了吗?降级攻击的窗口存在吗?

思考题

第 1 题(概念)

认证(Authentication)和授权(Authorization)的区别是什么?请举一个”认证了但没授权”的真实场景,以及一个”把认证误当成授权”导致的漏洞。

第 2 题(概念)

Digest 认证为什么死了?请说出至少两个原因,并指出它死亡的根本原因——它揭示的技术演进规律是什么?

第 3 题(设计)

你要设计一个面向第三方的开放 API,要求:

  1. 调用方需要证明”我是我”(认证)
  2. 传输内容不能被窃听
  3. 防止请求被”重放”(攻击者抓包后原样重发)
  4. 防止请求被”篡改”(改了金额)

请设计这套安全方案,说明每一层用什么机制,以及为什么单靠 HTTPS 还不够。

第 4 题(生活类比)

墨叔:进高档小区,和 HTTPS 的安全,有什么相似之处?

提示:进小区需要”门禁卡”,门禁系统会”验证卡的真伪”。想想:

  1. 门禁卡 = 证书的什么?
  2. 验证门禁卡 = TLS 的什么步骤?
  3. 如果门禁系统被”攻破”(比如能伪造卡),即使你的门禁卡是真的,小区还安全吗? 这对应 HTTPS 的什么局限?
  4. 如果你把门禁卡借给陌生人,他也能进——这对应什么?

第 5 题(更深一层的发问)

11.4 节说:一个”专门方案”往往会被”通用方案”淘汰(Digest 死于 TLS、pinning 死于 CT+审计)。

现在请你辩证地想:

  1. 这个规律是绝对的吗?有没有”专门方案”反而活得好好的例子?举例说明。
  2. 决定一个”专门方案”是被淘汰还是存活的,关键因素是什么?
  3. 这个思考,对你”设计技术方案”有什么指导意义?(提示:当你造一个轮子时,该问自己什么问题?)

参考答案

第 1 题答案

区别:认证回答”你是谁”,授权回答”你能做什么”。认证在先(先确定身份),授权在后(再决定权限)。

“认证了但没授权”的真实场景:

你登录了公司系统(认证通过,系统知道你是”员工小王”),但你想访问”财务部的工资表”(你没有授权)。系统应该返回 403 Forbidden(“我知道你是谁,但你没权限”)。这就是”认证成功、授权失败”的标准情况。

“把认证误当成授权”的漏洞:

一个管理后台,只检查了”用户是否登录”(认证),没有检查”用户是不是管理员”(授权)。结果:任何登录用户,只要猜对了后台的 URL(/admin/delete?id=1),就能执行删除操作。这就是”认证了 = 什么都能做”的错误假设,是大量越权漏洞的根源。

核心:认证和授权是两步,绝不能合并成一步。“登录成功”只完成了第一步,第二步(每个操作前检查权限)一步都不能省。

第 2 题答案

Digest 死亡的原因:

  1. 只防被动窃听,不防主动中间人:Digest 防抓包看密码,但攻击者可以在中间冒充服务器,Digest 挡不住。
  2. 只保护”认证”这一件事,而 TLS 保护”一切传输”——Digest 的价值被 TLS 完整覆盖(这是根本原因)。
  3. 复杂且生态差:nonce、qop、MD5 摘要协商复杂,实现参差,没人维护,越来越没人用。

根本原因 / 技术演进规律:

Digest 是一个”专门解决’密码明文传输‘“的方案,而它解决的问题,被一个更通用的方案(TLS)“顺便”解决了。 当通用方案普及后,专门方案的边际价值趋近于零,于是被淘汰。

规律:判断一个技术会不会被淘汰,关键不是它”好不好”,而是它解决的是不是一个”正在被通用方案接管”的问题。专门方案的生存空间,取决于它解决的问题有多”专属”、多”通用方案覆盖不到”。

第 3 题答案

分层方案:

需求机制为什么单靠 HTTPS 不够
认证(我是我)OAuth2 / API Key / 签名HTTPS 只证明”传输安全”,不证明”你是谁”,也不管”你有没有权限”
防窃听HTTPS(TLS)这一层 HTTPS 够用
防篡改HTTPS 的完整性校验 + 请求签名HTTPS 防”传输中被改”,但防不了”客户端自己传错”和”服务端日志被改”;且 HTTPS 在 MITM 代理下可能被解包,应用层签名更可靠
防重放nonce + 时间戳 + 签名HTTPS 本身不防重放!攻击者抓到一个合法的加密请求,原样重发,服务器照样处理

关键点:HTTPS 不防重放。

这是一个很多人不知道的事实:HTTPS 加密了内容,但攻击者不需要”看懂”内容,他只需要”原样重发”你抓到的密文请求,就能重放攻击(比如重复扣款)。所以防重放必须在应用层做:

防重放方案:请求里带 nonce(随机数)+ timestamp(时间戳)+ signature(签名)
- 服务器记录用过的 nonce(或用时间窗 + nonce),重复的 nonce 直接拒绝
- timestamp 超出允许窗口(如 ±5 分钟)直接拒绝
- signature = HMAC(secret, method + path + timestamp + nonce + body),防篡改

签名怎么防篡改:攻击者改了金额,但他没有你的 secret,算不出新的合法签名,服务器验签就会失败。

总结:

HTTPS 是”传输层”的安全,它保护”在路上”,不保护”在端点”、不防”重放”。 认证、授权、防重放、防篡改这些,是应用层的责任。“我用了 HTTPS 就安全了”是最常见、也最危险的错觉。

第 4 题答案

小区门禁HTTPS/TLS
门禁卡证书(Leaf 证书,绑定域名)
验证门禁卡TLS 的证书验证(证书链验证)
门禁系统被攻破、能伪造卡TLS 的局限:不防”你连的域名本身是假的”(钓鱼站也有真证书)、不防 CA 被攻破签发假证书
把卡借给陌生人,他也能进持有即身份——卡/token/证书只证明”持有”,不证明”本人”(呼应第 9 章 Cookie 的”持有即身份”缺陷)

核心洞察:

门禁卡(证书)解决的,是”验证这张卡是不是真的”(验证证书合法性),而不是”验证拿卡的人是不是该进来”(验证持有人身份)。 前者是技术能解决的(验签、验链),后者是技术难以解决的(“合法持有人主动外借”是人的问题)。

这对应 HTTPS 的局限:HTTPS 能保证”你连的服务器,确实拥有这个域名的合法证书”,但它不能保证”这个域名不是一个钓鱼域名”,也不能保证”合法服务器不被攻破”、不能保证”合法用户不主动泄露凭证”。

一句话:技术安全(验证卡是真的)能做到,但”信任”(相信持卡人、相信域名背后的意图)是技术到不了的边界。 这就是为什么”安全教育”和”技术安全”缺一不可。

第 5 题答案

1. 这个规律是绝对的吗?有反例。

不是绝对的。 有”专门方案”活得好好的例子:

  • 专门的东西不一定被通用方案取代,如果它解决的是”通用方案覆盖不到且足够重要”的问题。 比如:
    • 数据库专用协议(MySQL wire protocol、PostgreSQL 协议):它们没被”通用的 HTTP”取代,因为它们对”性能、事务、流式结果集”的要求,通用方案满足不了。
    • gRPC:它其实是”专门为服务间 RPC 优化的”,没被”通用 HTTP + JSON”取代,因为它在”高性能、强类型、流式”上做得更好。

2. 决定”专门方案”生死的关键因素:

关键是**“专门方案的优势,是否足以抵消’通用方案已经够用’的现实”。**

  • 被淘汰的专门方案(Digest、pinning):它们的优势(防明文密码、防假证书)不够大,通用方案(TLS、CT)已经”够用”了,于是专门方案的边际价值趋近于零。
  • 存活的专门方案(数据库协议、gRPC):它们的优势(性能、事务、类型)足够大,大到通用方案”够不着”,于是它们活了下来。

3. 对设计技术方案的指导意义:

当你”造轮子”(设计一个专门的方案/协议/工具)时,该问自己这几个问题:

  1. 我解决的问题,是不是”通用方案已经够用”的问题? 如果是,别造轮子——你会像 Digest 一样,被通用方案降维打击。
  2. 我的方案,有没有”通用方案够不着的、足够大的优势”? 比如性能高一个数量级、支持了某种独特的能力。如果没有,别造。
  3. 我的优势,能不能长期保持? 通用方案会进步(TLS 会越来越快、HTTP 会越来越强)。如果你的优势是”暂时的”,它会被追平。

总结:

“造轮子”的正确理由,不是”我做得比它好一点点”,而是”它根本覆盖不到我的场景,且这个场景足够重要”。 判断一个专门方案的价值,本质是判断”你的差异化优势 vs 通用方案够用的程度”之间的差距。差距大,造;差距小,别造。

这和第 16 章”亲手设计协议”直接相关——你在设计自己的协议之前,要先想清楚”我为什么不用 HTTP/2、gRPC、MQTT 这些现成的?我的差异化在哪?“


一页纸小结

认证/授权/加密是三件事:谁是你、你能干嘛、内容会不会被看。混了就出越权漏洞和”以为加密了”的错觉。

四层安全模型:网络层、TLS、HTTP 认证、应用层,各管一段,谁都不能替代谁。

Digest 之死:专门方案被通用方案(TLS)降维打击。判断技术生死的规律:它解决的是不是正在被通用方案接管的问题。

TLS 防什么不防什么:防窃听/篡改/冒充(服务器身份),不防钓鱼域名、不防服务端被攻破、不防企业 MITM 代理。

HTTPS 不防重放:攻击者不需看懂内容,原样重发即可。防重放靠应用层(nonce + timestamp + 签名)。

Go 大忌:InsecureSkipVerify: true 带进生产 = 拆掉 HTTPS 身份认证。


下一章:安全讲完了,现在要正面回答第 7 章埋下的那个问题——队头阻塞到底怎么解决? HTTP/2 的答案是”把文本变成二进制、把串行变成并发”。我要讲一个你可能没想过的设计细节:HTTP/2 的流 ID,为什么客户端只能用奇数、服务器只能用偶数? 这不是拍脑袋,是一个很深的工程考量。