之前有同事問為何要用基於JWT令牌的認證架構,然后近期又有童鞋在后台留言問微服務安全認證架構的實踐,因此我決定花兩篇推文來解答一下。為了答好這個話題,我們先來看看微服務的安全認證架構是如何演進而來的,從而更好地理解。
1 單塊階段(上)
首先,我們有必要再次了解下認證和授權這兩個基本概念:
認證,Authentication,識別你是誰。即在網站上用來識別某個用戶是否是注冊過的合法用戶。
授權,Authorization,識別你能做什么。即在網站上用來識別某個用戶是否有某方面的權限。
然后,這里還是引用我在波波老師的《Spring Boot與K8s雲原生應用開發》課程中學到的一個案例,來學習網站安全架構的演進。
假設我們把時間倒退回2006年,我們有一個叫做MyShop的網站,它的安全架構大概是下面這個樣子,我們暫且稱之為v1版本:
MyShop v1版本的認證操作
可以看到,這個v1版本的傳統安全認證架構,使用到了我們十分熟悉的Session + Cookie的模式來實現用戶的認證。即當一個注冊用戶通過登錄操作請求后,Web服務器通過向數據庫進行校驗用戶名和密碼,通過后就會向Session中添加一條記錄,然后返回給瀏覽器。在返回給瀏覽器的報文中,會將sessionId放在Cookie里頭。
MyShop v1版本的訪問操作
這樣一來,用戶在登錄之后再訪問網站的時候,就會將帶有sessionId的Cookie傳給Web服務器,而Web服務器就可以通過Cookie中的sessionId去Session記錄中檢查,如果沒有過時就認為其是認證的活躍用戶。
在ASP.NET Core中,提供了一個管理Session的中間件,我們可以在StartUp中注冊和使用這個中間件即可用來管理會話狀態。
參考資料:有關ASP.NET Core中的會話和狀態管理,這里是傳送門。
2 單塊階段(下)
v1版本上線測試之后,測試人員發現存在一個問題:登錄用戶會間歇性地退出登錄,而且會話還沒有超時。經過分析后發現,原來的Session記錄只會在登錄過的那台Web服務器上存在,而MyShop是以集群方式來部署的,由前置的Nginx代理服務器進行負載均衡地請求轉發。
針對這個問題,MyShop設計了下圖所示的v1.1版本的認證架構,又稱為黏性Session架構。也就是說,不管哪一次請求,Nginx都會將對應的sessionId發給對應的Web服務器進行處理,而不是均衡地輪流轉發。換句話說,Nginx服務器將會維護sessionId與各個Web服務器的Session之間的關聯,以保證在會話期間的Session綁定。
MyShop v1.1版本-黏性會話
就這樣,一晃兩三年又過了,MyShop v1.1的認證架構支持了它早期的快速發展。但是,隨着業務和用戶量的不斷擴展,它也逐漸暴露出穩定性和擴展性方面的問題。這些問題,歸根結底還是由黏性會話所造成的。
(1)穩定性:黏性會話會將用戶會話綁定到某個服務器上,如果我們要對這個服務器進行一些升級或改造又或服務器延遲或宕機,那么此服務器上的一波認證用戶信息就會瞬間消失,用戶必須重新登錄。
(2)擴展性:黏性會話使得Web服務器和Nginx負載均衡服務器上都保存了狀態,整體上屬於一個有狀態架構。隨着流量的增長,這些狀態同時給Web服務器和負載均衡器都會帶來較大的壓力。和無狀態的應用架構比起來,這種有狀態的應用架構比較難以擴展。
一般來說,常見的解決黏性會話的解決方案有以下幾種:
(1)會話同步復制:即各個Web服務器之間同步Session,但是會引入復雜性,整體的性能較低。
(2)無狀態會話:即Session數據不存在服務器端,而是存在瀏覽器端,但是存在數據泄露風險,且瀏覽器端對於Cookie的大小有限制(4KB)。
(3)集中狀態會話:即將Session集中存儲在某個存儲中,比如Memcached或Redis這種高性能緩存中。
因此,MyShop選擇了集中狀態會話的方式演進出了v1.5安全認證架構,如下圖所示:
MyShop v1.5版本-集中狀態會話
在v1.5版本中,Web服務器和Nginx服務器不再存儲會話狀態,轉而交由Redis進行統一存儲,從而提高了穩定性和擴展性。對於Redis來說,也可以采用高可用集群方案,業界也有很多可擴展的實踐案例。
畫外音:雖然是單塊時代發展出來的技術,但是無狀態會話和集中狀態會話卻是微服務安全認證架構的基礎。
3 微服務架構階段(上)
時光飛逝,時間來到了2015年,這期間MyShop的業務量也迅速的飛漲,期間互聯網的技術也發生了大變化。微服務架構、無線應用、SPA應用雨后春筍般的出現,MyShop的技術團隊也准備陸續應用實踐,進一步豐富和擴展業務渠道,賦能業務端。但是,微服務架構的安全認證授權也存在着一些挑戰:
微服務認證授權挑戰
(1)后台應用和服務眾多,如何對每一個服務進行認證和鑒權?傳統的用戶名&密碼以及Session/Cookie的方式還能夠適用嗎?
(2)前端的用戶入口眾多,如果每個入口都搞一套登錄認證,顯然成本高且難以擴展。有沒有一種SSO單點登錄的方案?經過MyShop技術團隊的分析,傳統的用戶名&密碼+Session/Cookie的方式無法直接套用在微服務架構上,但是可以借鑒之前的思路,他們提出了面向微服務架構的v2.0安全認證體系,如下圖所示:
MyShop v2.0版本-基於Token的認證
v2.0安全認證體系最大的變化就在於,將登陸認證抽取為一個獨立的API微服務AuthService,擁有一個獨立的UserDB。這個服務統一承擔登陸認證、用戶校驗、令牌頒發等職責。此外,v2.0版本還引入了Token作為服務調用認證鑒權的主要憑證。這里的話,v2.0采用的是一個透明令牌(也稱為引用令牌),即它是一個無意義的隨機字符串。這個令牌跟Auth Service上的一次登陸會話相關聯,后續也可以通過API去校驗這個令牌的合法性。
畫外音:其實這個Token令牌,就相當於一個SessionId。每個微服務拿到令牌,都可以去AuthService進行認證鑒權。
總結下來,v2.0的安全認證體系的步驟如下:
Step1.用戶通過某種客戶端(Web/SPA/H5/App等)進行登陸,AuthService通過比對用戶數據庫進行校驗;
Step2.AuthService校驗通過后會建立一個用戶會話Session(此Session和之前版本的類似,存在一個過期時間,可以存儲在AuthService所在的服務器上也可以存在Redis中),然后頒發一個Token給客戶端;
Step3.客戶端向后台微服務發請求,會帶上剛剛得到的Token;
Step4.微服務接收到用戶請求時,首先會向AuthService發出一個請求對這個Token進行合法性校驗;
Step5.AuthService校驗Token通過后會返回該用戶的詳情信息(如果微服務需要的話,可以拿到用戶的一些比如角色之類的信息);
Step6.微服務進行自己的邏輯處理,最終返回數據給客戶端;
畫外音:v2.0版本可以看做是v1.5的一個升級改造,專門針對微服務架構的場景進行了擴展,可以應對微服務架構存在的挑戰。它把登錄認證、令牌頒發等工作封裝在了AuthService中,其他微服務統一共用AuthService,經過擴展還可以實現SSO單點登錄。
4 微服務架構階段(下)
v2.0認證架構雖然可以解決問題,但是又引發了另外的問題:首先,每個微服務都需要實現部分認證鑒權的邏輯,使得微服務開發方無法聚焦於業務邏輯的開發。其次,認證鑒權邏輯分散在每個微服務當中,一方面會帶來不規范容易出錯的問題,另一方面也會有潛在的安全風險(比如某些開發人員可能會忘記校驗令牌)。為了解決上面提到的問題,同時考慮到微服務拆分后引入微服務API網關,MyShop技術團隊設計了下圖所示的v2.5認證架構:Token+Gateway結合方式
MyShop v2.5版本-基於Token+Gateway的認證
從上圖可以看出,該架構將每個微服務都要進行的部分認證鑒權的邏輯從微服務轉移到了網關中。即網關處負責拿到令牌向AuthService進行鑒權,通過后再將請求轉發到后端的微服務,微服務不再包含任何認證鑒權的邏輯。總體上,通過引入網關進行令牌的鑒權之后,大大減少了后端微服務開發方的職責,使得他們更專注於微服務的業務邏輯的開發。此外,引入網關之后,網關可以統一處理登錄客戶端的校驗,也便於實現SSO單點登錄,也為MyShop后續的微服務化和業務成長提供了基礎。
畫外音:v2.5版本應該是目前大多數團隊所采用的一種認證架構了。對,我司也是,不過Token類型使用的是JWT。
5 小結
本文通過一個MyShop的案例的演化介紹了微服務的安全認證架構是如何演進而來的,但是v2.5版本(Token+Gateway方式)總體上還是比較重,每個請求都還是需要到AuthService上去做認證鑒權的操作,這對於AuthService來說算是壓力比較大。針對這個問題,業界廣泛采用JWT這種輕量級的解決方案來重構安全認證架構。那么問題來了,JWT是什么?原理?實現方式?下一期騷年快答,為你解答這幾個問題。
參考資料
楊波,《Spring Boot與K8s雲原生應用開發》(極客時間課程,推薦學習)
楊波,《微服務架構160講》(極客時間課程,推薦學習)
Microsoft,《ASP.NET Core中的會話狀態管理》