From b941d8e8e3d4e942ffab5d9247f6fbee793145bd Mon Sep 17 00:00:00 2001 From: HomeDash Bot Date: Fri, 11 Sep 2026 22:27:01 +0800 Subject: [PATCH] =?UTF-8?q?fix(gateway):=20=E9=94=99=E8=AF=AF=E9=A1=B5?= =?UTF-8?q?=E5=BF=85=E9=A1=BB=E8=B7=B3=E8=BF=87=20auth=5Frequest=EF=BC=8C?= =?UTF-8?q?=E5=90=A6=E5=88=99=E4=B8=8A=E6=B8=B8=E6=95=85=E9=9A=9C=E4=BC=9A?= =?UTF-8?q?=E5=8F=98=E6=88=90=20401?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 实测:上游连接被拒(502)时 nginx 内部跳转 /500.html,而服务端的 auth_request 被这个内部请求继承 → 用户收到 nginx 默认 401 页, 自定义 404/500 永远显示不出来。 错误页及其静态资源一律 auth_request off,并加单测锁住。 --- admin-server/gateway.go | 17 ++++++++++++----- admin-server/gateway_test.go | 27 +++++++++++++++++++++++++-- 2 files changed, 37 insertions(+), 7 deletions(-) diff --git a/admin-server/gateway.go b/admin-server/gateway.go index 497c550..faa2b45 100644 --- a/admin-server/gateway.go +++ b/admin-server/gateway.go @@ -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) { diff --git a/admin-server/gateway_test.go b/admin-server/gateway_test.go index 8757f04..bda8d70 100644 --- a/admin-server/gateway_test.go +++ b/admin-server/gateway_test.go @@ -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) + } + } +}