用户断开 VPN,在内部站点遇到 403,并报告代理坏了。能产生那个页面的有三件不同的事:源站拒绝了请求,正向代理拒绝打开隧道,或者代理索要它自己的凭据。每一件都是对不同问题的正确回答,把它们压平成一张通用故障页,会毁掉你唯一的证据。按做出拒绝的组件来分类,再比较每个组件实际使用的网络路径。

说出产生 403 的组件

HTTP 状态码属于发送它的那一方。源站返回 403 时,它已经解析了请求并判定调用方可能没有该资源,这是授权层面的回答,不是传输问题。正向代理对 CONNECT 回答 403 时,它根本没有建立隧道,上游也没有被接触过。它回答 407 时,要的是它自己的凭据,与应用的登录无关。

What you observeWhat it means
Origin replies 403The origin understood the request and denied it
Proxy refuses CONNECT with 403The proxy declined to open that tunnel
Proxy replies 407Proxy authentication is required first
Gateway replies 502 or 504Upstream failure or timeout, not a permission decision

RFC 9110 定义了这些角色,包括代理特有的挑战。先读响应自身的状态和任何 Proxy-Authenticate 头,再读正文,因为友好的 HTML 错误页通常会掩盖是哪个组件写的。

CONNECT 隧道对字节中继隐藏了什么

代理对 CONNECT 返回 200 之后,它在转发字节,浏览器与源站保持自己的 TLS 会话。因此转发字节的代理读不到该会话里加密的 403,也永远不会成为拒绝的作者。隧道打开之前的拒绝则完全是另一件事,产生在更低一层。

终止 TLS 的反向代理处在不同位置:它拥有 TLS 边界,解析 HTTP,可以合法地改写或拦截响应。这种能力是配置选择,不是代理的普遍属性,也不授权绕过应用的访问策略。读响应是一种能力;决定调用方能看到什么仍然是源站的职责。

实际后果是,在解释 403 之前必须知道自己运行的是三种形态中的哪一种。给流代理增加 HTTP 检查规则不会让它获得可见性,而指望字节中继去解释源站的拒绝,会让你查错日志。

比较代理的出口路径与浏览器的隧道地址

连接到 VPN 的浏览器不会自动把单独的代理服务器搬进那条隧道。源站看到的是 代理的出口地址,如果代理仍从本地默认路由出去,受保护的站点看到的是一个从未被配置为受信任的地址。笔记本上的内层 VPN 地址对那个源站不可见。这是可用的隧道仍然从对其他人行为正常的站点拿到 403 的最常见原因。

分别测试两条路径,以免地址被混淆。

curl -sS -o /dev/null -w 'status=%{http_code}\n' --noproxy '*' https://198.51.100.20/private
curl -sS -o /dev/null -w 'status=%{http_code}\n' -x http://203.0.113.10:3128 https://198.51.100.20/private

第一个请求显示源站对你自己的地址如何回答,第二个显示它对代理的地址如何回答。一对相同的 403 指向源站的授权规则;200 之后跟着 403 指向代理路径。去问源站所有者规则集实际放行哪些源地址,而不是假设 VPN 地址池被包含在内。

用能隔离边界的请求逐一测试

隔离代理层:向它请求一件它无法以应用理由拒绝的事。对代理允许到达的主机发起 CONNECT 并丢弃输出,就能看出隧道准入是否工作。

curl -sSv -o /dev/null -x http://203.0.113.10:3128 https://198.51.100.20/ 2>&1 | grep -E 'CONNECT|HTTP/1.1|Proxy-Authenticate'

如果 CONNECT 打印 200,随后是 TLS 握手,说明代理准入正常,后来的 403 来自源站。如果打印 407,用 --proxy-user 提供代理凭据并在对站点下任何结论之前重试。如果在 CONNECT 行上打印 403,要检查的边界就是代理自己的允许列表,应用并未参与。

把凭据挡在 shell 历史和粘贴的转录之外。在命令行输入的代理密码对其他本地用户可见,而被捕获的带凭据的 CONNECT 跟踪比没有跟踪更糟。

保持结论可读,而不是重写页面

一旦知道是哪个组件拒绝的,就保留那个回答。把源站的 403 重新标成 504 超时、或替换成成功页的反向代理,会把授权决定藏在可用性故事后面,并把下一个响应者送到错误的团队。自定义错误页在保留状态并补充上下文时是好的。

缓存也值得同样的谨慎。RFC 9111 规定了共享缓存可以如何复用响应,为某个调用方产生的拒绝不得当作许可交给另一个人。如果确实存储拒绝响应,保持它们私有且限定范围,绝不要仅仅为了减少上游流量而添加公共新鲜度规则。

以书面结论收尾,写明组件、它产生的状态以及源站看到的源地址。记录确切的请求、生效的代理设置和时间,好让下一份报告能与同一批证据比对,而不是凭记忆重新推导。那份记录能在几秒内回答下一份报告,并阻止同一个 403 被再次诊断为 VPN 中断。

相关文章