Authorization 是客户端向源服务器证明身份用的。
最常见的 Basic 方案只是 base64,不是加密——它的唯一保护是「走 HTTPS」。
协商家族讲完了,从这一节开始讲身份。
注意它和 6.4.15 的 Proxy-Authorization 是一对,但目标不同。
一、格式:方案加凭据
值永远是两段:认证方案名,一个空格,然后是凭据。
Basic 是最简单的一种,凭据是「用户名:密码」的 base64。
Demo 里 bozai:secret 是 12 字节,base64 之后是 Ym96YWk6c2VjcmV0,16 字节。
膨胀 1.33 倍——因为 base64 每三个字节变成四个字符。
base64 不是加密。它没有密钥,任何人不带任何工具就能解回来。
Demo 里那一行「解回来是 'bozai:secret'」就是现场解的,用的还是标准库的 b64decode,一行代码。
二、为什么它还活着
因为简单。服务端不用管会话、不用签名、不用过期时间,每次请求带上就行——天然无状态。
代价是每个请求都要带密码。
这就是它必须配 HTTPS 的原因:明文 HTTP 上发 Basic,等于把密码写在明信片上。
另一个代价是没法主动登出。
客户端不发这个头就等于登出,但已经发出去的那些请求记录里,密码还是明文可解的。
所以真实系统里的做法是:用一次登录换一个短期令牌,之后带的是 Bearer 加令牌,
而不是 Basic 加密码。Bearer 不在 47 个字段的取值清单里,因为它是 OAuth 2.0 定义的方案名。
三、认证失败要回 401 加 WWW-Authenticate
不是 403。
401 是「你还没证明你是谁」,403 是「你是谁我知道了,但不许」。
这个区别在 4.4.2 和 4.4.3 已经说过,这里再落一次。
WWW-Authenticate 里必须写方案名,通常还带一个 realm(领域名)。
浏览器看到这一行才会弹那个原生的登录框。
realm 必须写 ASCII。Demo 里写的是 MySite。
写成中文会乱码——协议里的字符串默认按 latin-1 解,这个坑 4.4.2 踩过一次。
四、动手 Demo:四种情形:不带、正确、密码错、方案错
服务端要求 Basic 认证,realm 写 MySite。 客户端分别不带凭据、带正确凭据、带错误密码、带一个错误的认证方案(Bearer)去打它。
if not auth:
return reply(u'HTTP/1.1 401 Unauthorized',
[(u'WWW-Authenticate', u'Basic realm="MySite"')],
b'need auth')
scheme, _, val = auth.partition(u' ')
if scheme.lower() != u'basic':
return reply(u'HTTP/1.1 401 Unauthorized', ...)
plain = base64.b64decode(val.strip()).decode(u'utf-8')
u_, _, p_ = plain.partition(u':')
if u_ == USER and p_ == PASS:
return reply(u'HTTP/1.1 200 OK', [], u'欢迎回来'.encode(u'utf-8'))
实跑输出:
--------------------------------------------------------------------
Authorization:Basic 只是 base64,不是加密
--------------------------------------------------------------------
用户名密码拼起来是 `bozai:secret`,12 字节
base64 之后是 `Ym96YWk6c2VjcmV0`,16 字节
base64 是加密吗 不是。任何人不带密钥就能解回来
解回来是 'bozai:secret'
【不带凭据】
状态 HTTP/1.1 401 Unauthorized
正文 need auth
【正确凭据】
状态 HTTP/1.1 200 OK
正文 欢迎回来
服务端解出 'bozai:secret'
【错误密码】
状态 HTTP/1.1 401 Unauthorized
正文 wrong password
【错误的认证方案】
状态 HTTP/1.1 401 Unauthorized
正文 bad scheme
`WWW-Authenticate: Basic realm="MySite"` 里的 realm 必须写 ASCII。
写成中文会乱码——因为协议里的字符串默认按 latin-1 解,
这一点在 4.4.2 已经踩过一次。
Basic 认证的唯一保护是「走 HTTPS」。明文 HTTP 上发 Basic,
等于把密码写在明信片上寄出去——base64 连一层纸都算不上。
不带凭据:401,正文 need auth,配 WWW-Authenticate: Basic realm="MySite"。
正确凭据:200,正文「欢迎回来」,服务端解出来的就是 'bozai:secret'——原文,没有任何加密痕迹。
错误密码:401,正文 wrong password。
注意这里和服务端「查无此人」回了同一个状态码,
这是对的——不能让攻击者靠状态码区分「用户不存在」和「密码错」。
错误的方案(Bearer):401,正文 bad scheme。方案名不认就直接拒,连解都不解。
30 秒实验
- 把
realm改成中文,重跑,看响应头的字节变成什么。 - 把 base64 那串改掉一个字符,看服务端解出来的是什么。
- 想一想:如果服务端同时支持 Basic 和 Bearer,
WWW-Authenticate该怎么写?
常见误区
- 「base64 是一种加密」——它是编码,可逆,且不需要密钥。
- 「Basic 认证失败要回 403」——是 401,配上 WWW-Authenticate。
- 「Basic 认证很安全因为密码被编码了」——唯一的安全来自 HTTPS。
自测三问(点开对答案)
1. `Basic` 方案的凭据是什么格式? —— 「用户名:密码」用 UTF-8 编码后做 base64。 2. 为什么说 base64 不是加密? —— 它没有密钥,decode 一行代码就能还原,只能防止肉眼直接读。 3. 认证失败该回 401 还是 403? —— 401 配 `WWW-Authenticate`,表示「你还没证明你是谁」。身份说完了。下一个字段解决一个很实际的性能问题:正文值不值得发。
- ☐ (本节完,下一节 6.4.6 Expect)