微服務權限處理,為什么這么難?
應用拆分微服務后,一個不可避免的問題就是權限問題。拆分后的各個微服務如何處理權限,怎么處理才能保證滿足業務的需求,怎么處理才能保持架構的簡單及可維護?今天的文章,讓我們來深入微服務架構下的權限處理問題,看看這個沒有最佳實踐的領域,如何能夠針對業務需求來設計的較為優雅。
1.先來理解幾個名詞
關于權限,可能有很多相關名詞,而且這些名詞也很可能會讓大家混淆或是疑惑(為了避免讓大家產生疑惑,我就不列舉這些名詞了)。
但本文中會用到幾個核心的有關權限的詞,理解這幾個詞對于理解本文必不可少,所以關于權限處理至少有下述三個名詞需要大家先來理解。(1)認證。(2)鑒權。(3)授權。先來說說認證。我們都會用到各種各樣的系統,并且在進入系統之前都需要你輸入用戶名/密碼,點擊登錄后,后臺做的事就是認證,及驗證你的用戶名/密碼是否正確,能否登錄系統。這就是認證做的事。再來說說鑒權。同樣的系統,不同的人登錄成功后,擁有的權限是不一樣的,在你進行操作時,后臺會驗證你是否能夠進行相應的操作,這就是鑒權,及校驗用戶是否有相應資源的操作權限。最后來說說授權。不同的用戶擁有不同的權限是通過系統授權來實現的,授權就是授予相關用戶或角色資源操作的權限。當然授權可能還包含對第三方系統的授權,本文暫不討論此類場景。其實對于大多數應用系統來說,權限處理最關鍵的就是要解決上述三個詞所描述的問題,即系統權限處理要做的事就是認證、鑒權和授權。
2.認證、鑒權和授權,在單體應用中如何實現?
在上文中,我們說明了權限問題的核心就是解決認證、鑒權和授權問題。想要知道微服務架構中如何處理權限問題,不如讓我們先看看在單體應用中是如何處理上述問題的?讓我們來分別說明。(1)認證單體應用中,對于用戶登錄,會先校驗用戶的登錄信息(用戶名/密碼),這其中可能會涉及到密碼的加密處理,一般的判斷是加密后相應的密碼是否與數據庫中存儲的密碼相等,如果相等的話,則登錄成功。用戶登錄成功的話,一般會返回用戶登錄成功相關憑證。如果是JWT的話,則會返回Token,如果采用的是會話的話,則會通過Set-Cookie返回SessionId給客戶端。
(2)鑒權單體應用一般會通過攔截器(Spring Security、Apache Shrio本質上都是攔截器),攔截用戶請求,這里同樣會根據鑒權方案是JWT還是HTTP會話分別處理,如果是JWT的話,則會通過解密來獲得用戶信息,如果是采用的會話的方式,則會根據會話ID,從存儲中(這里一般是Redis)來獲得用戶信息,不論是哪種方案,最終都是根據用戶信息來對相應的請求進行權限校驗。這里需要注意的是,鑒權一般有兩種方式,但都是基于角色的。一種是基于角色的隱式鑒權,即根據角色直接判斷是否擁有相應資源的操作權限,比如你的角色是管理員,你就可以刪除用戶,你的角色是普通用戶,你只能查看用戶信息。這種一般在簡單的系統中比較適用,常用的方式是通過注解,表明哪個接口可以給哪個角色訪問。但這種方式在復雜的系統中就會變得難以維護。另一種是基于角色的精準鑒權,此種鑒權方案,一般會給角色分配明確的權限,相應的鑒權方式為根據用戶角色查出具體的權限集合,然后進行進一步判斷。此種方式在復雜系統中更加有效方便。
(3)授權在單體應用中,授權即修改用戶相關角色信息,或者修改角色相關權限信息。一般在用戶重新登錄后,最新的權限信息生效。
3.在微服務架構下如何實現上述功能?
其實在微服務中,權限處理的認證、授權功能實現,跟單體應用沒什么區別。
從微服務的基于業務拆分的原則上,對于用戶相關的業務,我們都會單獨創建一個用戶服務,用戶服務作為一個單獨的微服務,在實現認證、授權功能時,和單體應用保持一致即可,即相應的用戶登錄信息校驗、用戶角色修改、角色的權限修改,在用戶服務中獨立實現即可。所以在微服務架構下權限處理唯一與單體應用不同的便是鑒權方式。鑒權方式為什么變得不同了呢?因為鑒權不再集中在單體應用中了,鑒權被分散在了各個微服務中。所以現在問題的關鍵就變成了從哪里獲取用戶相關信息,然后在哪里進行鑒權。下面讓我們來看幾種微服務架構下的鑒權方案:第一種方案為,從用戶服務獲取用戶信息,然后各個微服務分別鑒權。如下圖所示:
上圖中有三個微服務,分別是倉儲服務、訂單服務、以及用戶服務,對于每個微服務的訪問都需要進行鑒權,相應的鑒權方式為,所有對微服務的訪問在經過網關后都統一先訪問用戶服務獲取用戶相關信息,包括角色信息、角色權限信息,然后分別在各個微服務中進行鑒權,判斷用戶是否有權限進行相應的操作。
上述方式的優點是實現簡單,對比單體應用的實現來說,只是換了個地方獲取用戶信息,權限相關的判斷還是保留在各個微服務中。對應的缺點也很明顯,用戶服務需要保證高性能、高可靠以及可擴展,并且隨著服務的增多,每個服務都需要與用戶服務交互,服務間調用可能會顯得略加繁瑣,不夠簡單優雅,同時每個微服務中的權限判斷邏輯也都重復冗余。綜上,個人并不是很推薦這種方式。其實對于第一種方案,我們略作修改,就可以得到一個略微優雅的方案,如下圖,看第二種方案:
第二種方案為從用戶服務獲取用戶信息,然后在網關進行鑒權,這樣整個架構看起來是不是就清爽了許多?(美好的東西總是看起來簡單的)
大家可能看到,這里將請求往具體的微服務透傳的時候,帶上了用戶信息,這個用戶信息可以放在HTTP Header中。為什么透傳的時候要帶上用戶信息呢?因為后端具體的微服務業務可能需要獲取當前用戶的信息,但這里并不是用來鑒權,只是業務邏輯中用到了而已。第二種方式的優點是架構簡單,對于鑒權來講服務間調用交互更加簡潔,并且后端微服務不需要冗余鑒權業務邏輯。缺點同樣是用戶服務需要保證高性能、高可靠以及可擴展,并且鑒權邏輯需要在網關實現。微服務的理想狀態是高內聚低耦合,每個服務專注于自己的事,各個服務間功能獨立,獨立演進,所以針對微服務鑒權還有沒有更好的方案?請看第三種方案:
鑒權要做的事是判斷用戶有沒有相應資源的操作權限,所以從微服務的基于業務拆分的原則基礎上,鑒權放在用戶服務是不是更合適?所以上述方案將鑒權放在了用戶服務來實現,我們不再需要調用用戶服務來獲取用戶信息然后進行鑒權了,直接將用戶的請求轉發到用戶服務,在用戶服務中統一處理鑒權。此種方案下網關要做的事也將更加專注,只做請求的轉發即可,后端各個微服務也只需專注于自己的業務即可。
此種方案,在我看來是比較推薦的方案,并且應該能滿足微服務架構下大多數系統的權限處理需求。當然缺點是對用戶服務的高性能、高可靠以及可擴展提出了更高的要求。除了上述三種鑒權方式,微服務架構下可能還有其他鑒權方式,比如共享用戶信息緩存的方式,這種方式下,各個微服務統一從緩存獲取用戶信息,然后分別在各自進行鑒權。基于此種方式也很容易想到一個優化的變種,即從網關獲取緩存的用戶信息,然后在網關進行鑒權。
諸如此類的鑒權方式,我個人是不太推薦的,當然采用此類鑒權方式的團隊可能也有他們的考慮,更多的可能是出于性能的考慮。至于我為什么不推薦諸如此類的方式呢,我覺得我們在做系統架構的時候需要有自己的原則,其實對于很多問題我們可以有許多種解決方法,哪種方案比哪種好可能并不好說,甚至團隊在方案的選擇上還會有所爭執,但如果在架構設計上團隊有自己的指導原則的話,可能能夠幫助我們在方案的選擇上達成共識。個人在架構設計上的原則包括但不限于可維護原則、可擴展原則、簡單性原則等,所以與其說此類方案不好,倒不如說這并不符合我的設計原則。
寫在最后應用拆分微服務后,一個不可避免的問題是權限問題。
權限問題要解決的核心問題是認證、鑒權和授權。在說明微服務架構下如何處理權限問題之前,我們先看了單體應用下是如何處理權限問題的認證、鑒權和授權的。對于微服務來講,權限問題的處理與單體應用中的處理最大的不同是鑒權的方式,至于認證、授權的實現與單體應用并無太大不同,在微服務中的用戶服務下實現即可。微服務下鑒權處理為什么變得不同了呢?因為鑒權不再集中在單體應用中了,鑒權被分散在了各個微服務中。
所以問題的關鍵是微服務下該從哪里獲取用戶相關信息,然后在哪里進行鑒權。我們詳細介紹了微服務架構下三種鑒權的實現,第一種方式是從用戶服務獲取用戶信息,然后在各個微服務中鑒權。第二種方式是從用戶服務獲取用戶信息,然后在網關統一鑒權。第三種方式不再需要獲取用戶信息,直接通過網關轉發請求,在用戶服務進行統一鑒權,各個服務的職責劃分更加明確,也是個人推薦的實現方式。除此之外,還介紹了一些其他的鑒權實現方案,問題的解決方案并不唯一,有時候也很難說出哪種更好,我們要有自己的設計原則。
聊技術,不止于技術。