Nginx 配置指令的執行順序(十)

2021-09-06 20:19:49 字數 4817 閱讀 5736

執行在post-rewrite階段之後的是所謂的preaccess階段。該階段在access階段之前執行,故名preaccess.

標準模組 ngx_limit_req 和 ngx_limit_zone 就執行在此階段,前者可以控制請求的訪問頻度,而後者可以限制訪問的併發度。這裡我們僅僅和它們打個照面,後面還會有機會專門接觸到這兩個模組。

前面反覆提到的標準模組 ngx_realip 其實也在這個階段註冊了處理程式。有些讀者可能會問:「這是為什麼呢?它不是已經在post-read階段註冊處理程式了嗎?」我們不妨通過下面這個例子來揭曉答案:

server 

}

與先看前到的例子相比,此例最重要的區別在於把 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 

}

這裡,我們在rewrite階段將 $remote_addr 的值儲存到了使用者變數$addr中,然後再輸出。因為rewrite階段先於preaccess階段執行,所以當 ngx_realip 模組尚未在preaccess階段改寫**位址時,最初的**位址就已經在rewrite階段被讀取了。上例的實際請求結果證明了我們的結論:

其中第一行除錯資訊

是 set 語句讀取 $remote_addr 變數時產生的。資訊中的字串"127.0.0.1"便是 $remote_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變數賦值之後,所以已經太遲了。

而最後一行資訊

則是 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

這裡,我們在/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

nginx

細心的讀者會在 nginx 錯誤日誌檔案中看到類似下面這一行的出錯資訊:

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

然而,如果我們把上例中的satisfy all語句更改為satisfy any

location /test

結果則會完全不同:

$ curl localhost:8080/test

something important

即請求反而最終通過了驗證。這是因為在any方式下,access階段只要有乙個模組通過了驗證,就會認為請求整體通過了驗證,而在上例中,ngx_lua 模組的 access_by_lua 語句總是會通過驗證的。

在配置了satisfy any的情況下,只有當access階段的所有模組的處理程式都拒絕訪問時,整個請求才會被拒,例如:

location /test

此時訪問/test介面才會得到403 forbidden錯誤頁。這裡,post-access階段參與了access階段各模組處理程式的「或關係」的實現。

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

Nginx 配置指令的執行順序(八)

前面我們詳細討論了rewrite access和content這三個最為常見的 nginx 請求處理階段,在此過程中,也順便介紹了執行在這三個階段的眾多 nginx 模組及其配置指令。同時可以看到,請求處理階段的劃分直接影響到了配置指令的執行順序,熟悉這些階段對於正確配置不同的 nginx 模組並實...

Nginx 配置指令的執行順序(七)

來看乙個ngx static模組服務磁碟檔案的例子。我們使用下面這個配置片段 location 同時在本機的 var www 目錄下建立兩個檔案,乙個檔案叫做index.html,內容是一行文字this is my home 另乙個檔案叫做hello.html,內容是一行文字hello world....

Nginx 配置指令的執行順序(八)

前面我們詳細討論了rewrite access和content這三個最為常見的 nginx 請求處理階段,在此過程中,也順便介紹了執行在這三個階段的眾多 nginx 模組及其配置指令。同時可以看到,請求處理階段的劃分直接影響到了配置指令的執行順序,熟悉這些階段對於正確配置不同的 nginx 模組並實...