Nginx 的 Http 模塊介紹(中)
在前面介紹完 post-read、server-rewrite、find-config、rewrite 和 post-rewrite 階段后,我們將繼續學習 preaccess 和 access 兩個階段,中間會涉及部分模塊,一同進行說明。
1. preaccess 階段
在 preaccess 階段在 access 階段之前,主要是限制用戶的請求,比如并發連接數(limit_conn模塊)和每秒請求數(limit_req 模塊)等。這兩個模塊對于預防一些攻擊請求是很有效的。
1.1 limit_conn 模塊
ngx_http_limit_conn_module 模塊限制單個 ip 的建立連接的個數,該模塊內有 6 個指令。分別如下:
- limit_conn_zone: 該指令主要的作用就是分配共享內存。 下面的指令格式中 key 定義鍵,這個 key 往往取客戶端的真實 ip,zone=name 定義區域名稱,后面的 limit_conn 指令會用到的。size 定義各個鍵共享內存空間大小;
Syntax: limit_conn_zone key zone=name:size;
Default: —
Context: http
- limit_conn_status: 對于連接拒絕的請求,返回設置的狀態碼,默認是 503;
Syntax: limit_conn_status code;
Default: limit_conn_status 503;
Context: http, server, location
- limit_conn: 該指令實際限制請求的并發連接數。指令指定每個給定鍵值的最大同時連接數,當超過這個數字時被返回 503 (默認,可以由指令 limit_conn_status 設置)錯誤;
Syntax: limit_conn zone number;
Default: —
Context: http, server, location
- limit_conn_log_level: 當達到最大限制連接數后,記錄日志的等級;
Syntax: limit_conn_log_level info | notice | warn | error;
Default: limit_conn_log_level error;
Context: http, server, location
- limit_conn_dry_run: 這個指令是 1.17.6 版本中才出現的,用于設置演習模式。在這個模式中,連接數不受限制。但是在共享內存的區域中,過多的連接數也會照常處理。
Syntax: limit_conn_dry_run on | off;
Default: limit_conn_dry_run off;
Context: http, server, location
- limit_zone: 該指令已棄用,由 limit_conn_zone 代替,不再進行說明。
實例
http {
...
limit_conn_zone $binary_remote_addr zone=addr:10m
server {
...
location / {
limit_conn_status 500;
limit_conn_log_level warn;
# 限制向用戶返回的速度,每秒50個字節
limit_rate 50;
limit_conn addr 10;
}
}
}
1.2 limit_req 模塊
ngx_http_limit_req_module 模塊主要用于處理突發流量,它基于漏斗算法將突發的流量限定為恒定的流量。如果請求容量沒有超出設定的極限,后續的突發請求的響應會變慢,而對于超過容量的請求,則會立即返回 503(默認)錯誤。
該模塊模塊中比較重要的指令有:
- limit_req_zone 指令,定義共享內存, key 關鍵字以及限制速率
Syntax: limit_req_zone key zone=name:size rate=rate [sync];
Default: —
Context: http
- limit_req 指令,限制并發連接數
Syntax: limit_req zone=name [burst=number] [nodelay | delay=number];
Default: —
Context: http, server, location
- limit_req_log_level 指令,設置服務拒絕請求發生時打印的日志級別
Syntax: limit_req_log_level info | notice | warn | error;
Default:
limit_req_log_level error;
Context: http, server, location
- limit_req_status 指令, 設置服務拒絕請求發生時返回狀態碼
Syntax: limit_req_status code;
Default: limit_req_status 503;
Context: http, server, location
2. access 階段
2.1 限制某些 ip 地址的訪問權限
在 access 階段,我們可以通 allow 和 deny 指令來允許和拒絕某些 ip 的訪問權限,指令的用法如下:
Syntax: allow address | CIDR | unix: | all;
Default: —
Context: http, server, location, limit_except
Syntax: deny address | CIDR | unix: | all;
Default: —
Context: http, server, location, limit_except
實例
allow 和 deny 指令后面可以跟具體 ip 地址,也可以跟一個 ip 段, 或者所有(all)。
location / {
deny 192.168.1.1;
allow 192.168.1.0/24;
allow 10.1.1.0/16;
allow 2001:0db8::/32;
deny all;
}
2.2 auth_basic 模塊
auth_basic 模塊是基于 HTTP Basic Authentication 協議進行用戶名和密碼的認證,它默認是編譯進 Nginx 中的,可以在源碼編譯階段通過 --without-http_auth_basic_module 禁用該模塊。它的用法如下:
Syntax: auth_basic string | off;
# 默認是關閉的
Default: auth_basic off;
Context: http, server, location, limit_except
Syntax: auth_basic_user_file file;
Default: —
Context: http, server, location, limit_except
對于使用文件保存用戶名和密碼,二者之間需用冒號隔開,如下所示。
# comment
name1:password1
name2:password2:comment
name3:password3
在 centos 系統上,想要生成這樣的密碼文件,我們可以使用 httpd-tools 工具完成,直接使用 yum install
安裝即可。
$ sudo yum install httpd-tools
# 生成密碼文件user_file,帳號為user,密碼為pass
$ htpasswd -bc user_file user pass
接下來,我們只需要配置好 auth_basic 指令,即可對相應的 http 請求做好認證工作。
2.3 第三方訪問控制
第三方的訪問控制是用到了auth_request模塊,該模塊的功能是向上游服務轉發請求,如果上游服務返回的相應碼是 2xx,則通過認證,繼續向后執行;若返回的 401 或者 403,則將響應返回給客戶端。
auth_request 模塊默認是未編譯進 Nginx 中的,因此我們需要使用 --with-http_auth_reques_module
將該模塊編譯進 Nginx 中,然后我們才能使用該模塊。
Syntax: auth_request uri | off;
Default: auth_request off;
Context: http, server, location
Syntax: auth_request_set $variable value;
Default: —
Context: http, server, location
官方示例:
location /private/ {
auth_request /auth;
...
}
location = /auth {
# 上游認證服務地址
proxy_pass ...
proxy_pass_request_body off;
proxy_set_header Content-Length "";
proxy_set_header X-Original-URI $request_uri;
}
3. 實驗
3.1 limit_conn 模塊實驗
本次案例將使用 limit_conn 模塊中的指令完成限速功能實驗。實驗配置塊如下:
...
http {
...
# 沒有這個會報錯,必須要定義共享內存
limit_conn_zone $binary_remote_addr zone=addr:10m;
...
server {
server_name localhost;
listen 8010;
location / {
limit_rate 50;
limit_conn addr 1;
}
}
...
}
...
使用 limit_rate 指令用于限制 Nginx 相應速度,每秒返回 50 個字節,然后是限制并發數為 2,這樣方便展示效果。當我們打開一個瀏覽器請求該端口下的根路徑時,由于相應會比較慢,迅速打開另一個窗口請求同樣的地址,會發現再次請求時,正好達到了同時并發數為 2,啟動限制功能,第二個窗口返回 503 錯誤(默認)。
訪問第一次
快速訪問第二次
3.2 access 模塊實驗
我們做一個簡單的示例,配置如下。在 /root/test/web 下有 web1.html 和 web2.html 兩個靜態文件。訪問/web1.html 時,使用 allow all指令將所有來源的 ip 請求全部放過(當然也可以不寫);使用 deny all 會拒絕所有,所以訪問 /web2.htm l時,會出現 403 的報錯頁面。
server {
server_name access.test.com;
listen 8011;
root /root/test/web;
location /web1.html {
default_type text/html;
allow all;
# return 200 'access';
}
location /web2.html {
default_type text/html;
deny all;
# return 200 'deny';
}
}
訪問允許的 web1.html 頁面
訪問禁止的 web2.html
大家可以思考下,如果使用的是 return 指令呢,會有怎樣的結果?打開注釋,重新加載 Nginx 后,可以看到無論是訪問 /web1.html 還是 /web2.html,我們都可以看到想要的 return 指令中的結果。這是因為 return 指令所在的 rewrite 模塊先于 access 模塊執行,所以不會執行到 allow 和 deny 指令就直接返回了。但是對于訪問靜態頁面資源,則是在 content 階段執行的,所以會在經過 allow 和 deny 指令處理后才獲取靜態資源頁面的內容,并返回給用戶。
3.3 auth_basic 模塊實驗
$ htpasswd -bc /root/user.pass store @store.123!
$ cat /root/user.pass
store:$apr1$36xHOQGz$yk4O3roiW3SIJrkXFJ0pS1
在 Nginx 加入如下 server 塊的配置,監聽 8012 端口,并在 / 路徑中加入認證模塊。
server {
server_name auth_basic.test.com;
listen 8012;
location / {
auth_basic "my site";
auth_basic_user_file /root/user.pass;
}
}
重新加載 Nginx 后,訪問主機的 8012 端口的根路徑時,就會發現需要輸入賬號和密碼了。成功輸入賬號和密碼后,就可以看到 Nginx 的歡迎頁了。
使用 auth_basic 模塊認證
認證成功后的頁面
4. 小結
本篇文章中,我們介紹了 Http 請求的 11 個階段中的中間階段,分別為 preaccess 和 access 階段。在這兩個階段中,主要生效的指令有limit_conn、limit_req、allow、deny 等,這些指令幾乎都是用來做訪問控制的,用來限制 Nginx 的并發連接、訪問限速、設置訪問白名單以及認證訪問等。這些安全限制在線上環境是十分必要的,必須要限制惡意的請求以及添加白名單操作。