Nginx 配置指令的执行顺序(十)

运行在 post-rewrite 阶段之后的是所谓的 preaccess 阶段。该阶段在 access 阶段之前执行,故名preaccess.

 

    标准模块 ngx_limit_req 和 ngx_limit_zone 就运行在此阶段,前者可以控制请求的访问频度,而后者可以限制访问的并发度。这里我们仅仅和它们打个照面,后面还会有机会专门接触到这两个模块。

 

    前面反复提到的标准模块 ngx_realip 其实也在这个阶段注册了处理程序。有些读者可能会问:“这是为什么呢?它不是已经在 post-read 阶段注册处理程序了吗?”我们不妨通过下面这个例子来揭晓答案:

    server {        listen 8080;         location /test {            set_real_ip_from 127.0.0.1;            real_ip_header X-Real-IP;             echo "from: $remote_addr";        }    }

与先看前到的例子相比,此例最重要的区别在于把 ngx_realip 的配置指令放在了 location 配置块中。前面我们介绍过,Nginx 匹配 location 的动作发生在 find-config 阶段,而 find-config 阶段远远晚于 post-read阶段执行,所以在 post-read 阶段,当前请求还没有和任何 location 相关联。在这个例子中,因为ngx_realip 的配置指令都写在了 location 配置块中,所以在 post-read 阶段,ngx_realip 模块的处理程序没有看到任何可用的配置信息,便不会执行来源地址的改写工作了。

 

    为了解决这个难题,ngx_realip 模块便又特意在 preaccess 阶段注册了处理程序,这样它才有机会运行location 块中的配置指令。正是因为这个缘故,上面这个例子的运行结果才符合直觉预期:

    $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test    from: 1.2.3.4

不幸的是,ngx_realip 模块的这个解决方案还是存在漏洞的,比如下面这个例子:

    server {        listen 8080;         location /test {            set_real_ip_from 127.0.0.1;            real_ip_header X-Real-IP;             set $addr $remote_addr;            echo "from: $addr";        }    }

这里,我们在 rewrite 阶段将 $remote_addr 的值保存到了用户变量 $addr 中,然后再输出。因为 rewrite阶段先于 preaccess 阶段执行,所以当 ngx_realip 模块尚未在 preaccess 阶段改写来源地址时,最初的来源地址就已经在 rewrite 阶段被读取了。上例的实际请求结果证明了我们的结论:

    $ curl -H 'X-Real-IP: 1.2.3.4' localhost:8080/test    from: 127.0.0.1

输出的地址确实是未经改写过的。Nginx 的“调试日志”可以进一步确认这一点:

    $ grep -E 'http script (var|set)|realip' logs/error.log    [debug] 32488#0: *1 http script var: "127.0.0.1"    [debug] 32488#0: *1 http script set $addr    [debug] 32488#0: *1 realip: "1.2.3.4"    [debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F    [debug] 32488#0: *1 http script var: "127.0.0.1"

其中第一行调试信息

    [debug] 32488#0: *1 http script var: "127.0.0.1"

是 set 语句读取 $remote_addr 变量时产生的。信息中的字符串 "127.0.0.1" 便是 $remote_addr 当时读出来的值。

 

    而第二行调试信息

    [debug] 32488#0: *1 http script set $addr

则显示我们对变量 $addr 进行了赋值操作。

 

    后面两行信息

    [debug] 32488#0: *1 realip: "1.2.3.4"    [debug] 32488#0: *1 realip: 0100007F FFFFFFFF 0100007F

是 ngx_realip 模块在 preaccess 阶段改写当前请求的来源地址。我们看到,改写后的新地址确实是期望的1.2.3.4. 但很明显这个操作发生在 $addr 变量赋值之后,所以已经太迟了。

 

    而最后一行信息

    [debug] 32488#0: *1 http script var: "127.0.0.1"

则是 echo 配置指令在输出时读取变量 $addr 时产生的,我们看到它的值是改写前的来源地址。

 

    看到这里,有的读者可能会问:“如果 ngx_realip 模块不在 preaccess 阶段注册处理程序,而在rewrite 阶段注册,那么上例不就可以工作了?”答案是:不一定。因为 ngx_rewrite 模块的处理程序也同样注册在 rewrite 阶段,而前面我们在 (二) 中特别提到,在这种情况下,不同模块之间的执行顺序一般是不确定的,所以 ngx_realip 的处理程序可能仍然在 set 语句之后执行。

 

    一个建议是:尽量在 server 配置块中配置 ngx_realip 这样的模块,以避免上面介绍的这种棘手的例外情况。

 

    运行在 preaccess 阶段之后的则是我们的另一个老朋友,access 阶段。前面我们已经知道了,标准模块ngx_access、第三方模块 ngx_auth_request 以及第三方模块 ngx_lua 的 access_by_lua 指令就运行在这个阶段。

 

    access 阶段之后便是 post-access 阶段。从这个阶段的名字,我们也能一眼看出它是紧跟在 access 阶段后面执行的。这个阶段也和 post-rewrite 阶段类似,并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心自己完成一些处理工作。post-access 阶段主要用于配合 access 阶段实现标准 ngx_http_core 模块提供的配置指令 satisfy 的功能。

 

    对于多个 Nginx 模块注册在 access 阶段的处理程序,satisfy 配置指令可以用于控制它们彼此之间的协作方式。比如模块 A 和 B 都在 access 阶段注册了与访问控制相关的处理程序,那就有两种协作方式,一是模块 A 和模块 B 都得通过验证才算通过,二是模块 A 和模块 B 只要其中任一个通过验证就算通过。第一种协作方式称为 all 方式(或者说“与关系”),第二种方式则被称为 any 方式(或者说“或关系”)。默认情况下,Nginx 使用的是 all 方式。下面是一个例子:

    location /test {        satisfy all;         deny all;        access_by_lua 'ngx.exit(ngx.OK)';         echo something important;    }

这里,我们在 /test 接口中同时配置了 ngx_access 模块和 ngx_lua 模块,这样 access 阶段就由这两个模块一起来做检验工作。其中,语句 deny all 会让 ngx_access 模块的处理程序总是拒绝当前请求,而语句access_by_lua 'ngx.exit(ngx.OK)' 则总是允许访问。当我们通过 satisfy 指令配置了 all 方式时,就需要access 阶段的所有模块都通过验证,但不幸的是,这里 ngx_access 模块总是会拒绝访问,所以整个请求就会被拒:

    $ curl localhost:8080/test    <html>    <head><title>403 Forbidden</title></head>    <body bgcolor="white">    <center><h1>403 Forbidden</h1></center>    <hr><center>nginx</center>    </body>    </html>

细心的读者会在 Nginx 错误日志文件中看到类似下面这一行的出错信息:

    [error] 6549\#0: *1 access forbidden by rule

然而,如果我们把上例中的 satisfy all 语句更改为 satisfy any

    location /test {        satisfy any;         deny all;        access_by_lua 'ngx.exit(ngx.OK)';         echo something important;    }

结果则会完全不同:

    $ curl localhost:8080/test    something important

即请求反而最终通过了验证。这是因为在 any 方式下,access 阶段只要有一个模块通过了验证,就会认为请求整体通过了验证,而在上例中,ngx_lua 模块的 access_by_lua 语句总是会通过验证的。

 

    在配置了 satisfy any 的情况下,只有当 access 阶段的所有模块的处理程序都拒绝访问时,整个请求才会被拒,例如:

    location /test {        satisfy any;         deny all;        access_by_lua 'ngx.exit(ngx.HTTP_FORBIDDEN)';         echo something important;    }

此时访问 /test 接口才会得到 403 Forbidden 错误页。这里,post-access 阶段参与了 access 阶段各模块处理程序的“或关系”的实现。

 

    值得一提的是,上面这几个的例子需要 ngx_lua 0.5.0rc19 或以上版本;之前的版本是不能和 satisfy any 配置语句一起工作的。

时间: 2024-09-19 20:33:44

Nginx 配置指令的执行顺序(十)的相关文章

Nginx 配置指令的执行顺序(五)

Nginx 的 content 阶段是所有请求处理阶段中最为重要的一个,因为运行在这个阶段的配置指令一般都肩负着生成"内容"(content)并输出 HTTP 响应的使命.正因为其重要性,这个阶段的配置指令也异常丰富,例如前面我们一直在示例中广泛使用的 echo 指令,在 Nginx 变量漫谈(二) 中接触到的 echo_exec 指令,Nginx 变量漫谈(三) 中接触到的 proxy_pass 指令,Nginx 变量漫谈(五) 中介绍过的 echo_location 指令,以及 N

Nginx 配置指令的执行顺序(八)

 前面我们详细讨论了 rewrite.access 和 content 这三个最为常见的 Nginx 请求处理阶段,在此过程中,也顺便介绍了运行在这三个阶段的众多 Nginx 模块及其配置指令.同时可以看到,请求处理阶段的划分直接影响到了配置指令的执行顺序,熟悉这些阶段对于正确配置不同的 Nginx 模块并实现它们彼此之间的协同工作是非常必要的.所以接下来我们接着讨论余下的那些阶段.       前面在 (一) 中提到,Nginx 处理请求的过程一共划分为 11 个阶段,按照执行顺序依次是 po

Nginx 配置指令的执行顺序(一)

大多数 Nginx 新手都会频繁遇到这样一个困惑,那就是当同一个 location 配置块使用了多个 Nginx 模块的配置指令时,这些指令的执行顺序很可能会跟它们的书写顺序大相径庭.于是许多人选择了"试错法",然后他们的配置文件就时常被改得一片狼藉.这个系列的教程就旨在帮助读者逐步地理解这些配置指令背后的执行时间和先后顺序的奥秘.       现在就来看这样一个令人困惑的例子:     ? location /test {    ?     set $a 32;    ?     e

Nginx 配置指令的执行顺序(四)

 ngx_lua 模块提供了配置指令 access_by_lua,用于在 access 请求处理阶段插入用户 Lua 代码.这条指令运行于 access 阶段的末尾,因此总是在 allow 和 deny 这样的指令之后运行,虽然它们同属 access 阶段.一般我们通过 access_by_lua 在 ngx_access 这样的模块检查过客户端 IP 地址之后,再通过 Lua 代码执行一系列更为复杂的请求验证操作,比如实时查询数据库或者其他后端服务,以验证当前用户的身份或权限.       我

Nginx 配置指令的执行顺序(六)

前面我们在 (五) 中提到,在一个 location 中使用 content 阶段指令时,通常情况下就是对应的 Nginx 模块注册该 location 中的"内容处理程序".那么当一个 location 中未使用任何 content 阶段的指令,即没有模块注册"内容处理程序"时,content 阶段会发生什么事情呢?谁又来担负起生成内容和输出响应的重担呢?答案就是那些把当前请求的 URI 映射到文件系统的静态资源服务模块.当存在"内容处理程序"

Nginx 配置指令的执行顺序(三)

如前文所述,除非像 ngx_set_misc 模块那样使用特殊技术,其他模块的配置指令即使是在 rewrite 阶段运行,也不能和 ngx_rewrite 模块的指令混合使用.不妨来看几个这样的例子.       第三方模块 ngx_headers_more 提供了一系列配置指令,用于操纵当前请求的请求头和响应头.其中有一条名叫 more_set_input_headers 的指令可以在 rewrite 阶段改写指定的请求头(或者在请求头不存在时自动创建).这条指令总是运行在 rewrite 阶

Nginx 配置指令的执行顺序(十一)

紧跟在 post-access 阶段之后的是 try-files 阶段.这个阶段专门用于实现标准配置指令 try_files 的功能,并不支持 Nginx 模块注册处理程序.由于 try_files 指令在许多 FastCGI 应用的配置中都有用到,所以我们不妨在这里简单介绍一下.       try_files 指令接受两个以上任意数量的参数,每个参数都指定了一个 URI. 这里假设配置了 N 个参数,则 Nginx 会在 try-files 阶段,依次把前 N-1 个参数映射为文件系统上的对

Nginx 配置指令的执行顺序(九)

紧接在 server-rewrite 阶段后边的是 find-config 阶段.这个阶段并不支持 Nginx 模块注册处理程序,而是由 Nginx 核心来完成当前请求与 location 配置块之间的配对工作.换句话说,在此阶段之前,请求并没有与任何 location 配置块相关联.因此,对于运行在 find-config 阶段之前的 post-read 和 server-rewrite 阶段来说,只有 server 配置块以及更外层作用域中的配置指令才会起作用.这就是为什么只有写在serve

Nginx 配置指令的执行顺序(七)

来看一个 ngx_static 模块服务磁盘文件的例子.我们使用下面这个配置片段:     location / {        root /var/www/;    } 同时在本机的 /var/www/ 目录下创建两个文件,一个文件叫做 index.html,内容是一行文本 this is my home:另一个文件叫做 hello.html,内容是一行文本 hello world. 同时注意这两个文件的权限设置,确保它们都对运行 Nginx worker 进程的系统帐户可读.