一文讀懂鑑權

服務端在響應客戶端請求的時候,會向客戶端推送一個 Cookie,這個 Cookie 記錄服務端上面的一些資訊,客戶端在後續的請求中攜帶這個 Cookie,服務端可以根據這個 Cookie 判斷該請求的上下文關係。

第一篇章

Cookie 的誕生及其特點眾所周知,web 伺服器是無狀態的,無狀態的意思就是伺服器不知道使用者上一次請求做了什麼,各請求之間是相互獨立的,客戶資訊僅來自於每次請求時攜帶的,或是伺服器自身儲存的且可以被所有請求使用的公共資訊。所以為了跟蹤使用者請求的狀態資訊,比如記錄使用者網上購物的購物車歷史記錄,Cookie 應運而生。

服務端在響應客戶端請求的時候,會向客戶端推送一個 Cookie,這個 Cookie 記錄服務端上面的一些資訊,客戶端在後續的請求中攜帶這個 Cookie,服務端可以根據這個 Cookie 判斷該請求的上下文關係。

Cookie 的出現,是無狀態化向狀態化過渡的一種手段。

以登入為例,使用者輸入賬戶名密碼,傳送請求到服務端,伺服器生成 Cookie 後傳送給瀏覽器,瀏覽器把 Cookie 以 k-v 的形式儲存到某個目錄下的文字檔案內,下一次請求同一網站時會把該 Cookie 傳送給伺服器。伺服器校驗該接收的 Cookie 與服務端的 Cookie 是否一致,不一致則驗證失敗。這是最初的設想。

一文讀懂鑑權

在瀏覽器中儲存的 Cookie 在下圖所示位置:

一文讀懂鑑權

Cookie的原理決定了他有以下特點:

1,儲存在客戶端,可隨意篡改,不安全

2,它的內容會隨著 http 互動傳接,影響效能,所以 Cookie 可儲存的資料不能過大,最大為 4kb

3,一個瀏覽器對於一個網站只能存不超過 20 個 Cookie,而瀏覽器一般只允許存放 300 個 Cookie

4,移動端對 Cookie 支援不友好

5,一般情況下儲存的是純文字,物件需要序列化之後才可以儲存,解析需要反序列化

二級域名之間的 Cookie 共享

還是以登入 Cookie 為例,比如現在有兩個二級域名,http://a。xxx。com(域名 A)和http://b。xxx。com(域名 B)。那麼域名 A 的登入 Cookie 在域名 B 下可以使用嗎?

預設情況下,域名 A 服務主機中生成的 Cookie,只有域名 A 的伺服器能拿到,其他域名是拿不到這個 Cookie 的,這就是僅限主機Cookie。

但是服務端可以透過顯式地宣告 Cookie 的 domian 來定義它的域,如上例子透過Set-Cookie將域名 A 的登入 Cookie 的 domain(域)設定成http://xxx。com(他們共同的頂級域名),path 設定成’/’,Set-Cookie:name=value;domain=xxx。com;path=’/’,那麼域名 B 便可以讀到。

在新的規範rfc6265 中,domain 的值會忽略任何前導點,也就是**xxx。com**和**。xxx。com**都可以在子域中使用。SSO(單點登入)也是依據這個原理實現的。

那比如現在又有兩個域名,a。b。e。f。com。cn (域名 1)和c。d。e。f。com。cn (域名 2),域名 2 想要讀到域名 1 的 Cookie,域名 1 可以宣告哪些 domain 呢?答案是。e。f。com。cn或。f。com。cn,瀏覽器不能接受 domian 為。com。cn 的 Cookie,因為 Cookie 域如果可以設定成字尾,那可就是峽谷大亂鬥了。

那如果域名 1 設定Set-Cookie:mykey=myvalue1;domain=e。f。com。cn;path=’/’

域名 2 設定Set-Cookie:mykey=myvalue2;domain=e。f。com。cn;path=’/’

那該域下 mykey 的值會被覆蓋為 myvalue2,很好理解,同一個域下,Cookie 的 mykey 是唯一的。通常,我們要透過設定正確的 domain 和 path,減少不必要的資料傳輸,節省頻寬。

Cookie-session 模式原理

隨著互動式 Web 應用的興起,Cookie 大小的限制以及瀏覽器對儲存 Cookie 的數量限制,我們一定需要更強大的空間來儲存大量的使用者資訊,比如我們這個網站是誰登入了,誰的購物車裡加入了商品等等,伺服器要儲存千萬甚至更多的使用者的資訊,Cookie 顯然是不行的。那怎麼辦呢?

試想,我們在伺服器端尋找一個空間儲存所有使用者會話的狀態資訊,並給每個使用者分配不同的“身份標識”,也就是sessionId ,再將這個 sessionId 推送給瀏覽器客戶端儲存在 Cookie 中記錄當前的狀態,下次請求的時候只需要攜帶這個 sessionId,服務端就可以去那個空間搜尋到該標識對應的使用者。**這樣做既能解決 Cookie 限制問題,又不用暴露使用者資訊到客戶端,大大增加了實用性和安全性。

那將使用者資訊儲存在哪呢?能否直接存在伺服器中?

如果存在伺服器中,這對伺服器說是一個巨大的開銷,嚴重的限制了伺服器的擴充套件能力。假設web伺服器做了負載均衡,使用者user1透過機器A登入該系統,那麼下一個請求如果被轉發到另一臺機器B上,機器B上是沒有存該使用者資訊的,所以也找不到sessionId,因此sessionId不應該儲存在伺服器上。這個時候redis/Memcached便出來解決該問題了,可以簡單的理解它們為一個快取資料庫。

當我們把 sessionId 集中儲存到一個獨立的快取伺服器上,所有的機器根據 sessionId 到這個快取系統裡去獲取使用者資訊和認證。那麼問題就迎刃而解了。

Cookie-session 工作原理流程圖

一文讀懂鑑權

根據其工作原理,你有沒有發現這個方式會存在一個什麼樣的問題?

那就是增加了單點登入失敗的可能性,如果負責 session 的機器掛了, 那整個登入也就掛了。但是一般在專案裡,負責 session 的機器也是有多臺機器的叢集進行負載均衡,增加可靠性。

Cookie-session 侷限性

1、依賴 Cookie,使用者可以在瀏覽器端禁用 Cookie

2、不支援跨端相容 app 等

3、業務系統不停的請求快取伺服器查詢使用者資訊,使得記憶體開銷增加,伺服器壓力過大

4、伺服器是有狀態的,如果是沒有快取伺服器的方式,擴容就非常困難,需要在多臺伺服器中瘋狂複製 sessionId

5、存在單點登入失敗的可能性

第二篇章

SSO(單點登入)三種類型

單點登入(Single Sign On),簡稱為 SSO。隨著企業的發展,一個大型系統裡可能包含 n 多子系統,使用者在操作不同的系統時,需要多次登入,很麻煩,單點登入就是用來解決這個問題的,在多個應用系統中,只需要登入一次,就可以訪問其他相互信任的應用系統。

一文讀懂鑑權

之前我們說過,單點登入是基於 cookie 同頂域共享的,那按照不同的情況可分為以下 3 種類型。

1、同一個站點下;

2、系統在相同的頂級域名下;

3、各子系統屬於不同的頂級域名

一般情況下一個企業有一個頂級域名,前面講過了,同一個站點和相同頂級域下的單點登入是利用了 Cookie 頂域共享的特性,相信大家已經明白這個流程,不再贅述。但如果是不同域呢?不同域之間 Cookie 是不共享的,怎麼辦?

CAS(中央認證服務)原理

這裡我們就要說一說 CAS(中央認證服務 )流程了,這個流程是單點登入的標準流程。它藉助一個單獨的系統專門做認證用,以下成為SSO系統。

它的流程其實跟 Cookie-session 模式是一樣的,單點登入等於說是每個子系統都擁有一套完整的 Cookie-session 模式,再加上一套 Cookie-session 模式的 SSO 系統。

一文讀懂鑑權

使用者訪問系統 a,需登入的時候跳到 SSO 系統,在 SSO 系統裡透過賬號密碼認證之後,SSO 的伺服器端儲存 session,,並生成一個 sessionId 返回給 SSO 的瀏覽器端,瀏覽器端寫入 SSO 域下的 Cookie,並生成一個生成一個 ST,攜帶該 ST 傳入系統 a,系統 a 用這個 ST 請求 SSO 系統做校驗,校驗成功後,系統 a 的伺服器端將登入狀態寫入 session 並種下系統 a 域下的 Cookie。之後系統 a 再做登入驗證的時候,就是同域下的認證了。

這時,使用者訪問系統 b,當跳到 SSO 裡準備登入的時候發現 SSO 已經登入了,那 SSO 生成一個 ST,攜帶該 ST 傳入系統 b,系統 b 用這個 ST 請求 SSO 系統做校驗,校驗成功後,系統 b 的伺服器端將登入狀態寫入 session 並設定系統 b 域下的 Cookie。可以看得出,在這個流程裡系統 b 就不需要再走登入了。

關於“跳到 SSO 裡準備登入的時候發現 SSO 已經登入了”,這個是怎麼做的呢,這就涉及 Oauth2 授權機制了,在這裡就不展開講,簡單提個思路,就是在系統 b 向 SSO 系統跳轉的時候讓它從系統 a 跳轉,攜帶系統 a 的會話資訊跳到 SSO,再透過重定向回系統 b。

關於 Oauth2,可移步阮一峰 的《OAuth 2。0 的四種方式》。

第三篇章

我們已經分析過 Cookie-session 的侷限性了,還有沒有更徹底的解決辦法呢?既然 SSO 認證系統的存在會增加單點失敗的可能性,那我們是不是索性不要它?這就是去中心化的思路,即省去用來儲存和校驗使用者資訊的快取伺服器,以另外的方式在各自系統中進行校驗。實現方式簡單來說,就是把 session 的資訊全部加密到 Cookie 裡,傳送給瀏覽器端,用 cpu 的計算能力來換取空間。

Json Web Token 模式

服務端不儲存 sessionId,使用者登入系統後,伺服器給他下發一個令牌(token),下一次使用者再次透過 Http 請求訪問伺服器的時候, 把這個 token 透過 Http header 或者 url 帶過來進行校驗。為了防止別人偽造,我們可以把資料加上一個只有自己才知道的金鑰,做一個簽名,把資料和這個簽名一起作為 token 傳送過去。這樣我們就不用儲存 token 了,因為傳送給使用者的令牌裡,已經包含了使用者資訊。當用戶再次請求過來的時候我用同樣的演算法和金鑰對這個 token 中的資料進行加密,如果加密後的結果和 token 中的簽名一致,那我們就可以進行鑑權,並且也能從中取得使用者資訊。

一文讀懂鑑權

對於服務端來說,這樣只負責生成 token , 然後驗證 token ,不再需要額外的快取伺服器儲存大量的 session,當面對訪問量增加的情況,我們只需要針對訪問需求大的伺服器進行擴容就好了,比擴充整個使用者中心的伺服器更節省。

假如有人篡改了使用者資訊,但是由於金鑰是不知道的,所以 token 中的簽名和被篡改後客戶端計算出來的簽名肯定是不一致的,也會認證失敗,所以不必擔心安全問題。

關於 token 的時效性,是這樣做的,首次登陸根據賬號密碼生成一個 token,之後的每次請求,服務端更新時間戳傳送一個新的 token,客戶端替換掉原來的 token。

JWT 工作原理流程圖

一文讀懂鑑權

JWT 有什麼優劣勢

弊端

1。jwt 模式的退出登入實際上是假的登入失效,因為只是瀏覽器端清除 token 形成的假象,假如用之前的 token 只要沒過期仍然能夠登陸成功

2。安全性依賴金鑰,一旦金鑰暴露完蛋

3。加密生成的資料比較長,相對來說佔用了更大的流量

優點

1。不依賴 Cookie,可跨端跨程式應用,支援移動裝置

2。相對於沒有單點登入的 cookie-session 模式來說非常好擴充套件

3。伺服器保持了無狀態特性,不需要將使用者資訊存在伺服器或Session中

4。對於單點登入需要不停的向 SSO 站點發送驗證請求的模式節省了大量請求

一文讀懂鑑權 | 《Linux就該這麼學》 (linuxprobe。com)