fix(gateway): 错误页必须跳过 auth_request,否则上游故障会变成 401

实测:上游连接被拒(502)时 nginx 内部跳转 /500.html,而服务端的
auth_request 被这个内部请求继承 → 用户收到 nginx 默认 401 页,
自定义 404/500 永远显示不出来。
错误页及其静态资源一律 auth_request off,并加单测锁住。
This commit is contained in:
HomeDash Bot
2026-09-11 22:27:01 +08:00
parent 1844586feb
commit b941d8e8e3
2 changed files with 37 additions and 7 deletions

View File

@ -191,13 +191,20 @@ func writeListeners(b *strings.Builder, cfg *GatewayConfig, defaultServer bool)
func writeErrorPages(b *strings.Builder, include404 bool) {
b.WriteString(" root /app/portal;\n")
// 关键:错误页与它自己的静态资源必须 `auth_request off`。
// 上游故障时 nginx 会内部跳转到 /500.html,而服务端的 auth_request 会被
// 这个内部请求继承 —— 于是"上游 502"最终以 401(认证失败)呈现给用户,
// 自定义错误页永远显示不出来。本轮实测踩到(日志里能看到 connect() refused,
// 客户端却收到 nginx 的默认 401 页)。
if include404 {
b.WriteString(" error_page 404 /404.html;\n location = /404.html { internal; }\n")
b.WriteString(" error_page 404 /404.html;\n")
b.WriteString(" location = /404.html { internal; auth_request off; }\n")
}
b.WriteString(" error_page 500 502 503 504 /500.html;\n location = /500.html { internal; }\n")
b.WriteString(" location ^~ /css/ { try_files $uri =404; }\n")
b.WriteString(" location = /favicon.svg { try_files $uri =404; }\n")
b.WriteString(" location = /favicon.ico { try_files $uri =404; }\n")
b.WriteString(" error_page 500 502 503 504 /500.html;\n")
b.WriteString(" location = /500.html { internal; auth_request off; }\n")
b.WriteString(" location ^~ /css/ { auth_request off; try_files $uri =404; }\n")
b.WriteString(" location = /favicon.svg { auth_request off; try_files $uri =404; }\n")
b.WriteString(" location = /favicon.ico { auth_request off; try_files $uri =404; }\n")
}
func writeProxyLocation(b *strings.Builder, r Route, path string, authOff bool) {

View File

@ -40,8 +40,13 @@ func TestRenderGatewayConfAuthPerRoute(t *testing.T) {
if open == "" {
t.Fatalf("缺少开放域名的 server 块")
}
if contains(open, "auth_request") {
t.Fatalf("未受保护的域名不应有 auth_request:\n%s", open)
// 断言的是"强制闸门" auth_request /_auth;,不是裸字符串:
// 错误页会合法地带 auth_request off;,用裸字符串会把正确配置判成错误。
if contains(open, "auth_request /_auth;") {
t.Fatalf("未受保护的域名不应有强制认证闸门:\n%s", open)
}
if !contains(open, "auth_request off;") {
t.Fatalf("错误页应显式关闭认证:\n%s", open)
}
// 兜底 server:未知域名必须被拒绝,而不是落进某条路由
if !contains(out, "default_server") || !contains(out, "return 404") {
@ -335,3 +340,21 @@ func TestRenderGatewayConfTLSHttp2Compatible(t *testing.T) {
t.Fatalf("证书路径未写入")
}
}
// 错误页必须 auth_request off:否则上游故障会以 401 呈现,自定义错误页永远看不到。
func TestRenderGatewayConfErrorPagesSkipAuth(t *testing.T) {
out, err := RenderGatewayConf(&GatewayConfig{ListenPort: 8081, AuthURL: "http://x/_auth",
LoginURL: "https://l/login", Routes: []Route{{Host: "a.com", Upstream: "http://a", RequireLogin: true}}})
if err != nil {
t.Fatalf("渲染失败: %v", err)
}
for _, want := range []string{
"location = /500.html { internal; auth_request off; }",
"location = /404.html { internal; auth_request off; }",
"location ^~ /css/ { auth_request off;",
} {
if !contains(out, want) {
t.Fatalf("错误页缺少 auth_request off(%q):\n%s", want, out)
}
}
}