前後端分離,分布式微服務下的挑戰——單點登錄
前後端分離
前後端分離,不僅僅是一種開發模式,也是一種服務的發布模式。前後端均可以獨立發布到任意可支撐的容器中。比如後端可以發布到Tomcat中,或者使用框架(如Spring Boot或Dubbo)自帶的內置容器,前端如果是靜態可直接基於nginx發布,如果是SSR,可基於Node環境運行,通過PM2工具進行管理。而如果是App,則直接發布到應用市場,由用戶自行去下載。
分布式
分布式,顧名思義,分布式的部署方式。傳統的web項目不考慮高並發,高可用等,通常都是部署在一臺獨立的伺服器上。而分布式要求項目必須考慮高並發,高可用的情況,同一個服務會分別發布到若干臺伺服器上。這些伺服器,通過負載均衡共同承擔並發壓力,相互備份冗餘以達到高可用的目的。
微服務
最早的分布式,總是將某個應用完整的分別部署,但是,這樣的一個結果就是:假設應用中,只有部分服務或接口,承受了較大的壓力,而另一部分幾乎無壓力。那麼,這種完整的分布式部署方式,無疑是對伺服器的一種浪費,且沒有完全發揮出伺服器的能力。所以,微服務要求對應用做更細化的拆分,再通過RPC框架(如Dubbo 或 Spring Cloud)等把它們連接起來構成一個整體。這樣,就可以對服務做出更靈活的部署方式,以更加充分的利用伺服器的資源。
挑戰
前後端分離,分布式微服務下,雖然可以應對更高的並發,但是,也給開發人員(基於不只是開發人員)帶來了更多的挑戰。我們就從一些簡單的場景入手,不斷的去深入挖掘,在這種新的服務模式下,都會有哪些挑戰,而我們又該如何去解決?
單點登錄
事實上,在傳統web項目中,單點登錄也可能是一個需要去解決的問題。假設某公司有兩個系統,如CMS內容管理系統,積分商城系統。某天,老闆說:為什麼我在內容管理系統已經登錄了,到了積分商城系統怎麼還要我重新登錄呢?這時候,就該考慮去做一個單點登錄了。不過,新服務模式下的單點登錄,似乎與原來又有所不同。因為要服務的運行模式更多樣化了。原來的服務可能都是放在Tomcat中的,而現在,可能有項目直接基於nginx發布,也有項目基於Node發布。事實上,更多的場景是在nginx或Node中的,這是因為,前後端分離的情況下,前端項目通常是獨立發布的,而後端項目,通常是一個或若干個項目,且它們的能力從外部看來,通常只是一系列接口的集合。那麼,我們就來聊聊,在這種前端項目以前端方式部署的情況下,如何實現單點登錄?需要明確的是,這裡,我們只討論各個前端項目之間如何去統一它們的登錄狀態。至於具體的登錄鑑權,以及和後臺接口交互時的鑑權,我們將在後續的Token機制中聊到。
單點登錄的基本原理
獨立服務中,我們的登錄通常是基於Session來做的,這是因為,在同一個服務中,雖然用戶每次請求使用的http或https協議是無狀態的,但是用戶的Session的id是相同的。因此,我們在伺服器中,在Session中存儲一個用戶狀態,用戶之後的每次請求就都可以讀取到這個狀態。而在多項目組合的情況下,這種狀態無法保持了,因為Session的id不同了,也就是說Session不同了。(先不考慮同一服務分布式部署的情況)那麼,在這種情況下,該如何保持多項目用戶狀態的統一呢?事實上,單點登錄的原理並不複雜,它做的,就是把認證中心做了抽離。即把所有項目的登錄都統一到了一個項目中。我們假設該項目名為user-center,另有一個項目cms,那麼,cms如何和user-center統一登錄狀態呢?很簡單,如下:
沒錯,就是做了一次跳轉,因為跳轉了一次user-center,那麼,就相當於又到了同一個服務中,此時,就得到了一個統一的用戶狀態。於是登錄狀態又得到了統一。那麼,該如何做登錄呢?事實上,上圖中為了便於理解,省略了一些內容,完整單點登錄邏輯如下:
圖看起來還挺複雜的,但是本質並不難,核心過程如下:
用戶訪問受限資源時,會由原項目跳轉到認證中心。認證中心檢查用戶登錄狀態,未登錄則跳轉到登錄頁要求登錄,用戶登錄時,依然使用認證中心統一驗證。用戶登錄驗證通過或本身已經登錄,則返回令牌給用戶。用戶通過令牌再次訪問受限資源時,通過認證中心校驗令牌有效性。有效則訪問,無效則重新登錄。分析上述流程,再結合前後端分離,可以整理出一個在前後端分離場景下更合理的單點登錄驗證模型,如下:
如圖中所示,只需要在cms無token或token無效的情況下,跳轉一次user-center取得token,此時,cms就可以通過token自行和後臺API接口服務完成相關的鑑權了。各個系統都通過user-center取得token,只要保證token是一致的,那麼,各系統通過後臺API取得的用戶狀態就是一致的。
代碼實現
以下提供一個單點登錄的代碼實現,涉及cms,admin,user-center三個系統。注意,在生成token時,後臺簡單使用了通過user-center發來的請求中得到的sessionId,這事實上是存在一系列安全問題的,這些問題,我們將在token機制中解決。介紹一下相關項目基本技術框架:後臺項目:Spring Boot。cms:nuxt框架,SSR模式(服務端渲染)。admin,user-center:nuxt框架,SPA模式(客戶端渲染)。在admin和cms添加中間件,用於校驗token有效性,如無token或token失效,則跳轉到user-center。在user-center中,添加一個token分發頁面,用於分發token並跳回。demo是一個最簡實現,僅供基本邏輯的演示。user-center是一個SPA項目,所以前端直接提供一個可訪問的頁面 /user/token用於token的分發,代碼如下:
<template><div> 頁面跳轉中……</div></template><script>exportdefault {name: "token", mounted() {let source = $nuxt.$route.query.sourcelet token = sessionStorage.getItem('TOKEN')if (!token) { token = 'abcd' sessionStorage.setItem('TOKEN', token) }if (source.indexOf('?') >= 0) { source += '&token=' + token } else { source += '?token=' + token }window.location.href = source } }</script><stylescoped></style>
該頁面的核心邏輯是:從sessionStorage中獲取或新生成token,把Token附帶到原地址後面,並跳轉回去。admin中包含一個中間件,路由變化時執行,代碼如下:
import Config from'@/config.json'exportdefaultasyncfunction({ app, redirect, req, route }) {let token = sessionStorage.getItem('TOKEN')if (!token) { token = route.query.tokenif (!token) {let currentUrl = window.location currentUrl = encodeURIComponent(currentUrl)return redirect(Config.tokenUrl + '?source=' + currentUrl) } sessionStorage.setItem('TOKEN', token) }}
核心邏輯是:從sessionStorage中獲取token,則嘗試從query參數中獲取,如還沒有,則將當前頁面地址包裝成source參數,跳轉到 user-center中的 /user/token去獲取token。cms中也包含一個中間件,功能上和admin中的差不多,但是,由於cms採用的是SSR模式,所以,寫法上有所不同,如下:
import Config from'@/config.json'exportdefaultasyncfunction({ app, redirect, req, route }) {let token = app.$cookies.get("TOKEN")if (!token) { token = route.query.tokenif (!token) {let currentUrl = '://' + req.headers.host + req.originalUrl currentUrl = encodeURIComponent(currentUrl)return redirect(Config.tokenUrl + '?source=' + currentUrl) } app.$cookies.set('TOKEN', token) }}
如下,邏輯上和admin是一致的,不同的是,由於在SSR模式下,sessionStorage、window等對象無法使用,所以我們需要通過cookies去存儲token,並通過req對象來獲取當前頁面的地址。
試運行
運行結果,你會發現,無論你打開admin還是cms,如果在當前項目中無法獲取到token,都會跳轉到user-center,取得token後再跳轉回原地址。也就是說,我們在三個項目中,拿到了統一的token。再加上後臺API的配合,就解決了單點登錄的問題。但是,demo中的寫法是簡單的,要實現真正能夠在生成上運行的單點登錄,還需要解決以下一些問題:
token如何生成?token如何保證安全性?這兩個問題,我們將在token機制中解決。