天天看點

認證鑒權與API權限控制在微服務架構中的設計與實作(四)

引言: 本文系《認證鑒權與API權限控制在微服務架構中的設計與實作》系列的完結篇,前面三篇已經将認證鑒權與API權限控制的流程和主要細節講解完。本文比較長,對這個系列進行收尾,主要内容包括對授權和鑒權流程之外的endpoint以及<code>Spring Security</code>過濾器部分踩坑的經曆。歡迎閱讀本系列文章。

首先還是照例對前文進行回顧。在第一篇 認證鑒權與API權限控制在微服務架構中的設計與實作(一)介紹了該項目的背景以及技術調研與最後選型。第二篇認證鑒權與API權限控制在微服務架構中的設計與實作(二)畫出了簡要的登入和校驗的流程圖,并重點講解了使用者身份的認證與token發放的具體實作。第三篇認證鑒權與API權限控制在微服務架構中的設計與實作(三)先介紹了資源伺服器配置,以及其中涉及的配置類,後面重點講解了token以及API級别的鑒權。

本文将會講解剩餘的兩個内置端點:登出和重新整理token。登出token端點的處理與<code>Spring Security</code>預設提供的有些’/logout’有些差別,不僅清空SpringSecurityContextHolder中的資訊,還要增加對存儲token的清空。另一個重新整理token端點其實和之前的請求授權是一樣的API,隻是參數中的grant_type不一樣。

除了以上兩個内置端點,後面将會重點講下幾種<code>Spring Security</code>過濾器。API級别的操作權限校驗本來設想是通過<code>Spring Security</code>的過濾器實作,特地把這邊學習了一遍,踩了一遍坑。

最後是本系列的總結,并對于存在的不足和後續工作進行論述。

在第一篇中提到了Auth系統内置的登出端點 <code>/logout</code>,如果還記得第三篇資源伺服器的配置,下面的關于<code>/logout</code>配置一定不陌生。

上面配置的主要作用是:

設定登出的URL

清空Authentication資訊

設定登出成功的處理方式

設定自定義的登出處理方式

當然在<code>LogoutConfigurer</code>中還有更多的設定選項,筆者此處列出項目所需要的配置項。這些配置項圍繞着<code>LogoutFilter</code>過濾器。順帶講一下<code>Spring Security</code>的過濾器。其使用了<code>springSecurityFillterChian</code>作為了安全過濾的入口,各種過濾器按順序具體如下:

SecurityContextPersistenceFilter:與SecurityContext安全上下文資訊有關

HeaderWriterFilter:給http響應添加一些Header

CsrfFilter:防止csrf攻擊,預設開啟

LogoutFilter:處理登出的過濾器

UsernamePasswordAuthenticationFilter:表單認證過濾器

RequestCacheAwareFilter:緩存request請求

SecurityContextHolderAwareRequestFilter:此過濾器對ServletRequest進行了一次包裝,使得request具有更加豐富的API

AnonymousAuthenticationFilter:匿名身份過濾器

SessionManagementFilter:session相關的過濾器,常用來防止session-fixation protection attack,以及限制同一使用者開啟多個會話的數量

ExceptionTranslationFilter:異常處理過濾器

FilterSecurityInterceptor:web應用安全的關鍵Filter

各種過濾器簡單标注了作用,在下一節重點講其中的幾個過濾器。登出過濾器排在靠前的位置,我們一起看下<code>LogoutFilter</code>的UML類圖。

認證鑒權與API權限控制在微服務架構中的設計與實作(四)

類圖和我們之前配置時的思路是一緻的,<code>HttpSecurity</code>建立了<code>LogoutConfigurer</code>,我們在這邊配置了<code>LogoutConfigurer</code>的一些屬性。同時<code>LogoutConfigurer</code>根據這些屬性建立了<code>LogoutFilter</code>。

<code>LogoutConfigurer</code>的配置,第一和第二點就不用再詳細解釋了,一個是設定端點,另一個是清空認證資訊。

對于第三點,配置登出成功的處理方式。由于項目是前後端分離,用戶端隻需要知道執行成功該API接口的狀态,并不用傳回具體的頁面或者繼續向下傳遞請求。是以,這邊配置了預設的<code>HttpStatusReturningLogoutSuccessHandler</code>,成功直接傳回狀态碼200。

對于第四點配置,自定義登出處理的方法。這邊需要借助<code>TokenStore</code>,對token進行操作。<code>TokenStore</code>在之前文章的配置中已經講過,使用的是JdbcTokenStore。首先校驗請求的合法性,如果合法則對其進行操作,先後移除<code>refreshToken</code>和<code>existingAccessToken</code>。

執行如下請求:

登出成功則會傳回200,将token和SecurityContextHolder進行清空。

在第一篇就已經講過,由于token的時效一般不會很長,而refresh token一般周期會很長,為了不影響使用者的體驗,可以使用refresh token去動态的重新整理token。重新整理token主要與<code>RefreshTokenGranter</code>有關,<code>CompositeTokenGranter</code>管理一個List清單,每一種grantType對應一個具體的真正授權者,refresh_ token對應的granter就是<code>RefreshTokenGranter</code>,而granter内部則是通過grantType來區分是否是各自的授權類型。執行如下請求:

在refresh_ token正确的情況下,其傳回的response和/oauth/token得到正常的響應是一樣的。具體的代碼可以參閱第二篇的講解。

在上一節我們介紹了内置的兩個端點的實作細節,還提到了<code>HttpSecurity</code>過濾器,因為登出端點的實作就是通過過濾器的作用。核心的過濾器主要有:

FilterSecurityInterceptor

UsernamePasswordAuthenticationFilter

SecurityContextPersistenceFilter

ExceptionTranslationFilter

這一節将重點介紹其中的<code>UsernamePasswordAuthenticationFilter</code>和<code>FilterSecurityInterceptor</code>。

筆者在剛開始看關于過濾器的文章,對于<code>UsernamePasswordAuthenticationFilter</code>有不少的文章介紹。如果隻是引入Spring-Security,必然會與<code>/login</code>端點熟悉。SpringSecurity強制要求我們的表單登入頁面必須是以POST方式向/login URL送出請求,而且要求使用者名和密碼的參數名必須是username和password。如果不符合,則不能正常工作。原因在于,當我們調用了HttpSecurity對象的formLogin方法時,其最終會給我們注冊一個過濾器<code>UsernamePasswordAuthenticationFilter</code>。看一下該過濾器的源碼。

<code>UsernamePasswordAuthenticationFilter</code>因為繼承了<code>AbstractAuthenticationProcessingFilter</code>才擁有過濾器的功能。<code>AbstractAuthenticationProcessingFilter</code>要求設定一個authenticationManager,authenticationManager的實作類将實際處理請求的認證。<code>AbstractAuthenticationProcessingFilter</code>将攔截符合過濾規則的request,并試圖執行認證。子類必須實作 attemptAuthentication 方法,這個方法執行具體的認證。

認證之後的處理和上登出的差不多。如果認證成功,将會把傳回的Authentication對象存放在SecurityContext,并調用SuccessHandler,也可以設定指定的URL和指定自定義的處SuccessHandler。如果認證失敗,預設會傳回401代碼給用戶端,也可以設定URL,指定自定義的處理FailureHandler。

基于<code>UsernamePasswordAuthenticationFilter</code>自定義的<code>AuthenticationFilte</code>還是挺多案例的,這邊推薦一篇博文Spring Security(五)–動手實作一個IP_Login,寫得比較詳細。

<code>FilterSecurityInterceptor</code>是filterchain中比較複雜,也是比較核心的過濾器,主要負責web應用安全授權的工作。首先看下對于自定義的<code>FilterSecurityInterceptor</code>配置。

從上述配置可以看到,在<code>FilterSecurityInterceptor</code>的位置注冊了<code>CustomSecurityFilter</code>,對于比對到<code>/oauth/check_token</code>,則會調用該進入該過濾器。下圖為<code>FilterSecurityInterceptor</code>的類圖,在其中還添加了<code>CustomSecurityFilter</code>和相關實作的接口的類,友善讀者對比着看。

認證鑒權與API權限控制在微服務架構中的設計與實作(四)

<code>CustomSecurityFilter</code>是模仿<code>FilterSecurityInterceptor</code>實作,繼承<code>AbstractSecurityInterceptor</code>和實作<code>Filter</code>接口。整個過程需要依賴<code>AuthenticationManager</code>、<code>AccessDecisionManager</code>和<code>FilterInvocationSecurityMetadataSource</code>。

<code>AuthenticationManager</code>是認證管理器,實作使用者認證的入口;<code>AccessDecisionManager</code>是通路決策器,決定某個使用者具有的角色,是否有足夠的權限去通路某個資源;<code>FilterInvocationSecurityMetadataSource</code>是資源源資料定義,即定義某一資源可以被哪些角色通路。

從上面的類圖中可以看到自定義的<code>CustomSecurityFilter</code>同時又實作了

<code>AccessDecisionManager</code>和<code>FilterInvocationSecurityMetadataSource</code>。分别為<code>SecureResourceFilterInvocationDefinitionSource</code>和<code>SecurityAccessDecisionManager</code>。下面分析下主要的配置。

上述代碼是<code>FilterSecurityInterceptor</code>中的實作,具體實作細節就沒列出了,我們這邊重點在于對自定義的實作進行講解。

 上面自定義的<code>CustomSecurityFilter</code>,與我們之前的講解是一樣的流程。主要依賴的三個接口都有在實作中執行個體化注入。看下父類的beforeInvocation方法,其中省略了一些不重要的代碼片段。

上面代碼可以看出,第一步是根據SecurityMetadataSource擷取配置的權限屬性,accessDecisionManager會用到權限清單資訊。然後判斷是否需要對認證明體重新認證,預設為否。第二步是接着決策管理器開始決定是否授權,如果授權失敗,直接抛出AccessDeniedException。

(1). 擷取配置的權限屬性

上面是getAttributes()實作的具體細節,将請求的URL取出進行比對事先設定的受限資源,最後傳回需要的權限、角色。系統在啟動的時候就會讀取到配置的map集合,對于攔截到請求進行比對。代碼中注釋比較詳細,這邊不多說。

(2). 決策管理器

上面的代碼是決策管理器的實作,其邏輯也比較簡單,将請求所具有的權限與設定的受限資源所需的進行比對,如果具有則傳回,否則抛出沒有正确的權限異常。預設提供的決策管理器有三種,分别為AffirmativeBased、ConsensusBased、UnanimousBased,篇幅有限,我們這邊不再擴充了。

補充一下,所具有的權限是通過之前配置的認證方式,有password認證和client認證兩種。我們之前在授權伺服器中配置了<code>withClientDetails</code>,是以用frontend身份驗證獲得的權限是我們預先配置在資料庫中的authorities。

Auth系統主要功能是授權認證和鑒權。項目微服務化後,原有的單體應用基于HttpSession認證鑒權不能滿足微服務架構下的需求。每個微服務都需要對通路進行鑒權,每個微應用都需要明确目前通路使用者以及其權限,尤其當有多個用戶端,包括web端、移動端等等,單體應用架構下的鑒權方式就不是特别合适了。權限服務作為基礎的公共服務,也需要微服務化。

筆者的設計中,Auth服務一方面進行授權認證,另一方面是基于token進行身份合法性和API級别的權限校驗。對于某個服務的請求,經過網關會調用Auth服務,對token合法性進行驗證。同時筆者根據目前項目的整體情況,存在部分遺留服務,這些遺留服務又沒有足夠的時間和人力立馬進行微服務改造,而且還需要繼續運作。為了适配目前新的架構,采取的方案就是對這些遺留服務的操作API,在Auth服務進行API級别的操作權限鑒定。API級别的操作權限校驗需要的上下文資訊需要結合業務,與用戶端進行商定,應該在token能取到相應資訊,傳遞給Auth服務,不過應盡量減少在header取上下文校驗的資訊。

筆者将本次開發Auth系統所涉及的大部分代碼及源碼進行了解析,至于沒有講到的一些内容和細節,讀者可以自行擴充。

API級别操作權限校驗的通用性

(1). 對于API級别操作權限校驗,需要在網關處調用時構造相應的上下文資訊。上下文資訊基本依賴于 token中的payload,如果資訊太多引起token太長,導緻每次用戶端的請求頭部長度變長。

(2). 并不是所有的操作接口都能覆寫到,這個問題是比較嚴重的,根據上下文集合很可能出現好多接口 的權限沒法鑒定,最後的結果就是API級别操作權限校驗失敗的是絕對沒有權限通路該接口,而通過不一定能通路,因為該接口涉及到的上下文根本沒法完全得到。我們的項目在現階段,定義的最小上下文集合能勉強覆寫到,但是對于後面擴增的服務接口真的是不樂觀。

(3). 每個服務的每個接口都在Auth服務注冊其所需要的權限,太過麻煩,Auth服務需要額外維護這樣的資訊。

網關處調用Auth服務帶來的系統吞吐量瓶頸

(1). 這個其實很容易了解,Auth服務作為公共的基礎服務,大多數服務接口都會需要鑒權,Auth服務需要經過複雜。

(2). 網關調用Auth服務,阻塞調用,隻有等Auth服務傳回校驗結果,才會做進一步處理。雖說Auth服務可以多執行個體部署,但是并發量大了之後,其瓶頸明顯可見,嚴重可能會造成整個系統的不可用。

從整個系統設計角度來講,API級别操作權限後期将會分散在各個服務的接口上,由各個接口負責其所需要的權限、身份等。Spring Security對于接口級别的權限校驗也是支援的,之是以采用這樣的做法,也是為了相容新服務和遺留的服務,主要是針對遺留服務,新的服務采用的是分散在各個接口之上。

将API級别操作權限分散到各個服務接口之後,相應的能提升Auth服務的響應。網關能夠及時的對請求進行轉發或者拒絕。

API級别操作權限所需要的上下文資訊對各個接口真的設計的很複雜,這邊我們确實花了時間,同時管理移動服務的好幾百操作接口所對應的權限,非常煩。!

本文的源碼位址:

GitHub:https://github.com/keets2012/Auth-service

碼雲: https://gitee.com/keets/Auth-Service

配置表單登入

Spring Security3源碼分析-FilterSecurityInterceptor分析

Core Security Filters

Spring Security(四)–核心過濾器源碼分析

來源:http://blueskykong.com/2017/10/26/security4/

♥ 作者:明志健緻遠

♠ 出處:http://www.cnblogs.com/study-everyday/

♦ 本文版權歸作者和部落格園共有,歡迎轉載,但未經作者同意必須保留此段聲明,且在文章頁面明顯位置給出原文連接配接,否則保留追究法律責任的權利。

♣ 本部落格大多為學習筆記或讀書筆記,本文如對您有幫助,還請多推薦下此文,如有錯誤歡迎指正,互相學習,共同進步。

繼續閱讀