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) + } + } +}