Nginx 在 if 语句之前未处理 auth_request

Nginx 在 if 语句之前未处理 auth_request

可能最简单的方式是通过例子来展示我遇到的问题,所以我会直接进入...

此代码片段将按原样在启用了重写/身份验证模块的 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 站点配置执行以下操作:

  1. /auth通过auth_request电话发送任何请求
  2. /auth 位置将请求发送到另一台服务器,该服务器添加标头x-user bar
  3. auth_request_set$foo根据x-user上面第 2 步中设置的上游标头值设置一个新的变量。
  4. 设置了一个新的标头x-test,其值为$foo
  5. 请求已发送至外部目的地。

响应完全符合我的预期,并确认变量$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_requestand/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

我已查看以下文档和问题:

答案1

if是一个问题,因为它是声明性配置中的命令式构造。

因此它并不总是按预期工作。更多信息可以在假如邪恶文章。

这种情况下,setauth_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/;
  }
}

相关内容