可能最简单的方式是通过例子来展示我遇到的问题,所以我会直接进入...
此代码片段将按原样在启用了重写/身份验证模块的 Nginx 上运行。因此希望这个问题可以在几乎任何 Nginx 安装上快速轻松地重现...
server {
listen 8081;
add_header x-user bar;
return 200;
}
server {
listen 8080;
location = /auth {
internal;
proxy_pass http://localhost:8081;
}
location / {
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
}
上述示例 Nginx 站点配置执行以下操作:
/auth
通过auth_request
电话发送任何请求- /auth 位置将请求发送到另一台服务器,该服务器添加标头
x-user bar
auth_request_set
$foo
根据x-user
上面第 2 步中设置的上游标头值设置一个新的变量。- 设置了一个新的标头
x-test
,其值为$foo
- 请求已发送至外部目的地。
响应完全符合我的预期,并确认变量$foo
设置正确:
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test'
HTTP/1.1 200 OK
x-test: bar
那么,问题来了……
我需要调整这个配置,以便如果上游标头的值不正确它将返回 403。
这似乎是一个简单的任务。所以我添加了一个if{}
条件来检查标题:
location / {
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
# this 'if' is the only part added to the original config
if ($foo != bar) {
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
条件if
评估为真,所以我得到了 403,这不是我所期望的。所以,这是行不通的:
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test'
HTTP/1.1 403 Forbidden
我意识到if 是邪恶的但我似乎只是用它来返回这应该没问题。我愿意使用任何方法来实现同样的目标——无论有没有如果,所以我愿意接受各种想法!!
我曾尝试过将auth_request
and/orif
语句移到块中server{}
,但似乎没有什么能够像我期望的那样进行评估。
进一步的故障排除/详细信息:
我已经证实问题在于在之前if
对进行了评估auth_request_set
location / {
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
if ($foo != bar) {
# x-test never gets set because $foo is null when if evaluates
add_header x-test $foo always;
add_header success false always;
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test|success'
HTTP/1.1 403 Forbidden
success: false
我已经验证这不是一个问题,如果使用set
而不是auth_request_set
。这可行(但没有达到目标):
# set works, but not auth_request_set
location / {
set $foo bar;
if ($foo != bar) {
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
此配置有效。set
之前已评估if
:
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test'
HTTP/1.1 200 OK
x-test: bar
auth_request
即使处于server{}
上下文中,行为仍会持续:
server {
listen 8081;
add_header x-user bar;
return 200;
}
server {
listen 8080;
auth_request /auth;
auth_request_set $foo $upstream_http_x_user;
location = /auth {
internal;
proxy_pass http://localhost:8081;
}
location / {
if ($foo != bar) {
return 403;
}
add_header x-test $foo;
add_header success true;
proxy_pass http://example.com/;
}
}
$ curl -s --head http://localhost:8080 | grep -E 'HTTP|x-test|success'
HTTP/1.1 403 Forbidden
success: false
我已查看以下文档和问题:
- 我可以在 nginx 中 auth_request 返回后比较 auth_request_set 设置的变量吗?
- https://stackoverflow.com/questions/73431103/cant-access-added-headers-using-nginx-auth-request-set
- Nginx $upsteam_cache_status 自定义标头不会出现
- auth_request 没有阻止返回指令,无法返回状态?
- https://www.nginx.com/resources/wiki/start/topics/depth/ifisevil
- http://nginx.org/en/docs/http/ngx_http_auth_request_module.html
- http://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if
- https://www.nginx.com/resources/wiki/start/topics/tutorials/config_pitfalls/
答案1
这if
是一个问题,因为它是声明性配置中的命令式构造。
因此它并不总是按预期工作。更多信息可以在假如邪恶文章。
这种情况下,set
和auth_request_set
发生在 nginx 请求处理的不同阶段,并且if
处理发生在这两个阶段之间。
不幸的是,我不知道如何在 nginx 中真正实现你想要的功能。也许这需要在代理请求的上游服务器中完成。
答案2
这并没有回答问题,请参阅 Tero 的回答真正的答案(剧透:if
是确实邪恶,即使与 一起使用return
)。
但是,作为提供解决方法的功能性答案:至少有一种方法可以使无效请求发送 403。proxy_pass 端点是动态的,具体取决于测试的标头。这似乎解决了用例...
upstream barhost {
server example.com;
}
map $foo $choose_upstream {
default 127.0.0.1:8999;
# cannot use hostnames here since they don't resolve. Using an upstream instead.
bar barhost;
}
server {
listen 8999;
return 403;
}
server {
listen 8081;
add_header x-user bar;
return 200;
}
server {
listen 8080;
auth_request /auth;
location = /auth {
internal;
proxy_pass http://localhost:8081;
}
location / {
auth_request_set $foo $upstream_http_x_user;
proxy_set_header Host example.com;
proxy_pass http://$choose_upstream/;
}
}