fix(gateway): 错误页必须跳过 auth_request,否则上游故障会变成 401
实测:上游连接被拒(502)时 nginx 内部跳转 /500.html,而服务端的 auth_request 被这个内部请求继承 → 用户收到 nginx 默认 401 页, 自定义 404/500 永远显示不出来。 错误页及其静态资源一律 auth_request off,并加单测锁住。
This commit is contained in:
@ -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) {
|
||||
|
||||
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user