1. 程式人生 > >一個“403”問題的產生及解決

一個“403”問題的產生及解決

最近在測試一個專案的時候,遇到了一個比較詭異的“403”問題問題。在經過不斷的查詢資料和諮詢大師級的人物之後,問題終於有了解決方案。現在就把我在整個過程中遇到的坑記錄下來,可以讓大家後續遇到類似問題有所參考。

問題描述

1.       測試的專案是一個程式設計考試的web專案,因此會涉及到使用者的登入,檢視題目,提交答案,檢視題目列表,檢視排名等一系列問題;

2.       專案開始時,只有一臺伺服器,這裡稱為伺服器A,包括了web + 判題服務兩部分。為了提高專案的效能,新增加了兩臺伺服器,這裡稱為伺服器B和伺服器C,同樣上面也都部署了web + 判題服務兩部分;

3.       在擴容前,也就是隻有伺服器A的時候,測試指令碼中的請求傳送的URL就是伺服器A的IP地址+開放的埠號,這裡就用“伺服器A_IP :port”來表示;

4.       在擴容之後,因為有了伺服器A,B,C,勢必需要進行負載均衡,於是引入了nginx來做負載均衡。所以在nginx中配置了域名server_name。那麼理所當然的,我的測試指令碼中的請求傳送的URL就是nginx中設定的域名了,這裡就用“server_name”來表示;

5.       那麼問題終於來了,擴容前進行測試一切請求包括,使用者的登入,檢視題目,提交答案,檢視題目列表,檢視排名等都很正常。但是擴容之後,在提交答案的這個請求返回狀態一直是“403 forbidden”。並且擴容前後測試指令碼並沒有改動,唯一的改動地方就是請求的URL從“伺服器A_IP :port”變成了“server_name”;

解決過程:

1.       剛看到這個問題,第一個想法是這樣:

由於nginx做了負載均衡,那麼一個指令碼中的3個請求:request1,request2,request3分別給均衡到了伺服器A,伺服器B,伺服器C上面。所以對於使用者perftest1來說,他的登入請求被分配到了伺服器A上,檢視題目的請求被分配到了伺服器B上,提交答案的請求被分配到了伺服器C上。被分配到伺服器C上的請求request3,仍然在苦苦等著屬於他自己的patsid,但是他自己的patsid卻在伺服器A上,他永遠也拿不到,所以產生了 403請求,被伺服器以不正確的patsid為由拒絕了perftest1的提交答案的請求。

   請教了一些同事後,他們給的建議是採用nginx的sticky模組。在多臺後臺伺服器的環境下,我們為了確保一個客戶只和一臺伺服器通訊,我們勢必使用長連線。使用什麼方式來實現這種連線呢,常見的有使用nginx自帶的ip_hash來做,我想這絕對不是一個好的辦法,如果前端是CDN,或者說一個區域網的客戶同時訪問伺服器,導致出現伺服器分配不均衡,以及不能保證每次訪問都粘滯在同一臺伺服器。如果基於cookie會是一種什麼情形,想想看,每臺電腦都會有不同的cookie,在保持長連線的同時還保證了伺服器的壓力均衡,nginx sticky值得推薦。如果瀏覽器不支援cookie,那麼sticky不生效,畢竟整個模組是給予cookie實現的。nginx sticky 模組工作流程圖如下:

但是,在nginx的配置檔案中新增了sticky模組之後,問題並沒有解決。方案一以失敗告終。

2.  採用tcpdump抓包檢視

在伺服器端添加了日誌,然後用“伺服器A_IP :port”作為請求URL的時候,日誌中的Parameters如下所示:

當用“server_name” 作為請求URL的時候,日誌中的Parameters如下所示:

[["f78494dc412a0455a5b67f68707b2b97",null]]

很明顯,用“server_name” 作為請求URL的請求引數中cookie和session都是空的,這也難怪會被403,給拒絕掉。

但是我的測試指令碼中明明是有Set-Cookie的。為了一探究竟,用tcpdump進行抓包:

可以發現cookie中的引數是我在指令碼中set進去的,登入獲得的patsid。但是卻被伺服器拒絕掉了。

3.  終於查到根源

諮詢了nginx大牛——劉成同學,終於找到了nginx的403問題的根源了,問題就在出現在請求引數的header中的這個引數'authenticity_token'。

因為nginx中的自身很多變數都是以下劃線標識的,所以遇到這個'authenticity_token'的時候,nginx為了防止混淆,他會把使用者發過來的過濾掉,所以就識別不到這個token值。解決方法是在nginx的配置的http模組中新增  underscores_in_headers on;(這個預設值是關閉的) 然後重啟nginx之後,就可以用指令碼返回正常的請求結果了。

HTTP頭部是否允許下劃線

語法:underscores_in_headers on | off;

預設:underscores_in_headers off;

配置塊:http、server

預設為off,表示HTTP頭部的名稱中不允許帶“_”(下劃線)。

可以檢視ngx_http_core_module 模組的說明。

至此,一個由nginx引發的403問題終於解決掉了,希望能給大家以幫助。