Proxy-Authenticate 只出现在 407 里,作用是告诉客户端「用哪种方案、
对哪个 realm 来认证我」。少了它,客户端只知道被拒,不知道该怎么补。
上一节的 Location 是给客户端指路,这一节是代理在门口拦人。
它和 6.4.15 的 Proxy-Authorization 是一对:一个要,一个给。
一、407 必须带它
401 和 407 都是「要认证」,区别是拦人的是谁:401 是目标服务器,407 是中间的代理。
相应地,401 用 WWW-Authenticate,407 用 Proxy-Authenticate。
Demo 里跑了两组 407:不带这一行的那组,响应只有 Proxy-Connection 和 Content-Length: 2;
带的那组多出 Proxy-Authenticate: Basic realm="gw"。
差别只有一行,后果差很远。客户端要发 Proxy-Authorization,得先知道方案名(Basic?Digest?
)和 realm,否则拼不出来。缺了这一行,它就只能干瞪眼——这正是很多自研代理「认证总也过不去」的原因。
二、两套凭据各走各的路
Proxy-Authorization 是发给代理的,Authorization 是发给目标服务器的。
关键在于:代理处理完 Proxy-Authorization 之后必须把它删掉再转发,否则凭据就漏给了源站。
D082 那组实测过这件事:客户端发了 9 个首部,源服务器只收到 3 个,
Proxy-Authorization 就在被丢掉的那 6 个里。
反过来,Authorization 要一路带到源站,代理不能动它。
这两个字段名字只差一个前缀,转发规则正好相反,很容易写错。
三、realm 只是个标签
realm="gw" 里的 gw 是「保护区名字」,纯粹给人看——浏览器弹登录框时会显示它。
它不参与任何加密或校验。
所以别指望 realm 能隔离权限。真正的隔离靠的是每一跳独立校验凭据,而不是靠名字取得好听。
还有一点:Basic 方案只是把账号密码 base64 一下,等于明文。
代理认证如果要过公网,必须跑在 TLS 里,否则同网段抓包就能看到密码。
四、动手 Demo:同一台代理,带不带那一行的差别
同一个 407 处理器跑两遍,唯一的开关是「响应里要不要塞 Proxy-Authenticate」, 看客户端能不能自己拼出凭据。
def challenge(with_hdr):
def fn(raw, st):
has = any(ln.lower().startswith(u'proxy-authorization:')
for ln in X.head_lines(raw))
if not has:
hd = [(u'Proxy-Connection', u'close')]
if with_hdr:
hd.insert(0, (u'Proxy-Authenticate', u'Basic realm="gw"'))
return X.reply(u'HTTP/1.1 407 Proxy Authentication Required', hd, b'no')
return X.reply(u'HTTP/1.1 200 OK', [], TEXT)
return fn
实跑输出:
--------------------------------------------------------------------
Proxy-Authenticate:告诉客户端「拿什么来找我认证」
--------------------------------------------------------------------
407 带 Proxy-Authenticate:False
HTTP/1.1 407 Proxy Authentication Required
Proxy-Connection: close
Content-Length: 2
Connection: close
服务端被访问 1 次
407 带 Proxy-Authenticate:True
HTTP/1.1 407 Proxy Authentication Required
Proxy-Authenticate: Basic realm="gw"
Proxy-Connection: close
Content-Length: 2
Connection: close
服务端被访问 1 次
差别只在一行,后果差很远:没有 `Proxy-Authenticate`,
客户端只知道「被拒了」,不知道用哪种方案、哪个 realm,
只能干瞪眼——它没法自己拼出 `Proxy-Authorization`。
有了这一行,客户端才知道该发 `Basic realm="gw"` 对应的凭据。
注意它和 401 的 `WWW-Authenticate` 是两套:
407 说的是「代理要你认证」,401 说的是「目标服务器要你认证」。
凭据也分两套,`Proxy-Authorization` 只发到第一跳代理,
不会跟着请求一路传到源服务器——这一点 D082 已经实测过。
不带的那组,407 里只有 Proxy-Connection 和 Content-Length: 2,客户端拿不到方案名和 realm。
带的那组多一行 Proxy-Authenticate: Basic realm="gw",客户端这才知道该发 Basic 凭据去哪一区。
两组服务端都被访问 1 次——因为客户端没有能力发起第二次,差别不在请求次数上,
在「能不能迈出第二步」上。
这条字段只出现在 407 里。在 200 或 401 里见到它,基本可以判定是配错了。
30 秒实验
- 把 realm 改成中文,看响应字节数会多多少(提示:要按 UTF-8 算)。
- 把
Basic换成Digest,观察客户端会不会换一种拼法。 - 想一想:为什么代理认证的凭据绝不能转发到源服务器?
常见误区
- 「407 和 401 是一回事」——一个拦人的是代理,一个是目标服务器,凭据也分两套。
- 「Proxy-Authorization 要一路带到源站」——必须在这一跳就被剥掉。
- 「realm 是权限范围」——只是个显示用的名字,不参与校验。
自测三问(点开对答案)
1. 哪个状态码必须带 `Proxy-Authenticate`? —— 407 Proxy Authentication Required。 2. `Proxy-Authorization` 会被转发到源服务器吗? —— 不会,代理消费完必须删掉,否则凭据就泄露了。 3. realm 的作用是什么? —— 给保护区起个名字,登录框显示用,不参与鉴权。认证拦人是硬拦。还有一种更客气的拒绝:「现在忙,等会儿再来」——那是 Retry-After。
- ☐ (本节完,下一节 6.5.6 Retry-After)