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