大家好,我是不才陳某~
今天這篇文章介紹一下Spring Cloud Gateway整合OAuth2.0實現認證授權,涉及到的知識點有點多,有不清楚的可以看下陳某的往期文章。
文章目錄如下:

微服務認證方案
微服務認證方案目前有很多種,每個企業也是大不相同,但是總體分為兩類,如下:
- 網關只負責轉發請求,認證鑒權交給每個微服務控制
- 統一在網關層面認證鑒權,微服務只負責業務
你們公司目前用的哪種方案?
先來說說第一種方案,有着很大的弊端,如下:
- 代碼耦合嚴重,每個微服務都要維護一套認證鑒權
- 無法做到統一認證鑒權,開發難度太大
第二種方案明顯是比較簡單的一種,優點如下:
- 實現了統一的認證鑒權,微服務只需要各司其職,專注於自身的業務
- 代碼耦合性低,方便后續的擴展
下面陳某就以第二種方案為例,整合Spring Cloud Gateway+Spring Cloud Security 整合出一套統一認證鑒權案例。
案例架構
開始擼代碼之前,先來說說大致的認證鑒權流程,架構如下圖:

大致分為四個角色,如下:
- 客戶端:需要訪問微服務資源
- 網關:負責轉發、認證、鑒權
- OAuth2.0授權服務:負責認證授權頒發令牌
- 微服務集合:提供資源的一系列服務。
大致流程如下:
1、客戶端發出請求給網關獲取令牌
2、網關收到請求,直接轉發給授權服務
3、授權服務驗證用戶名、密碼等一系列身份,通過則頒發令牌給客戶端
4、客戶端攜帶令牌請求資源,請求直接到了網關層
5、網關對令牌進行校驗(驗簽、過期時間校驗....)、鑒權(對當前令牌攜帶的權限)和訪問資源所需的權限進行比對,如果權限有交集則通過校驗,直接轉發給微服務
6、微服務進行邏輯處理
針對上述架構需要新建三個服務,分別如下:
| 名稱 | 功能 |
|---|---|
| oauth2-cloud-auth-server | OAuth2.0認證授權服 |
| oauth2-cloud-gateway | 網關服務 |
| oauth2-cloud-order-service | 訂單資源服務 |
案例源碼目錄如下:

認證授權服務搭建
很多企業是將認證授權服務直接集成到網關中,這么做耦合性太高了,這里陳某直接將認證授權服務抽離出來。
認證服務的搭建這里就不再細說了,上一篇文章中已經介紹的很清楚了:OAuth2.0實戰!使用JWT令牌認證!
新建一個oauth2-cloud-auth-server模塊,目錄如下:

和上篇文章不同的是創建了JwtTokenUserDetailsService這個類,用於從數據庫中加載用戶,如下:

為了演示只是模擬了從數據庫中查詢,其中存了兩個用戶,如下:
- user:具有ROLE_user權限
- admin:具有ROLE_admin、ROLE_user權限
要想這個生效,還要在security的配置文件SecurityConfig中指定,如下圖:

另外還整合了注冊中心Nacos,詳細配置就不貼了,可以看源碼。有不清楚Nacos,可以看之前文章:五十五張圖告訴你微服務的靈魂擺渡者Nacos究竟有多強?
案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
網關服務搭建
網關使用的是Spring Cloud Gateway,網關如何搭建這里就不再細說了,有不清楚的可以看之前文章:Spring Cloud Gateway奪命連環10問?
新建一個oauth2-cloud-gateway模塊,目錄如下圖:

1、添加依賴
需要添加幾個OAuth2.0相關的依賴,如下:

2、JWT令牌服務配置
使用JWT令牌,配置要和認證服務的令牌配置相同,代碼如下:

3、認證管理器自定義
新建一個JwtAuthenticationManager,需要實現ReactiveAuthenticationManager這個接口。
認證管理的作用就是獲取傳遞過來的令牌,對其進行解析、驗簽、過期時間判定。
詳細代碼如下:

邏輯很簡單,就是通過JWT令牌服務解析客戶端傳遞的令牌,並對其進行校驗,比如上傳三處校驗失敗,拋出令牌無效的異常。
拋出的異常如何處理?如何定制返回的結果?
這里拋出的異常可以通過Spring Cloud Gateway的全局異常進行捕獲,這個內容在Spring Cloud Gateway奪命連環10問?這篇文章有詳細介紹。下面只貼出關鍵代碼,如下:

4、鑒權管理器自定義
經過認證管理器JwtAuthenticationManager認證成功后,就需要對令牌進行鑒權,如果該令牌無訪問資源的權限,則不允通過。
新建JwtAccessManager,實現ReactiveAuthorizationManager,代碼如下:

這里的邏輯很簡單,就是取出令牌中的權限和當前請求資源URI的權限對比,如果有交集則通過。
①處的代碼什么意思?
這里是直接從Redis中取出資源URI對應的權限集合,因此實際開發中需要維護資源URI和權限的對應關系,這里不細說,為了演示,陳某直接在項目啟動的時候向Redis中添加了兩個資源的權限,代碼如下:

注意:實際開發中需要維護資源URI和權限的對應關系。
②處的代碼什么意思?
這處代碼就是取出令牌中的權限集合
③處的代碼什么意思?
這處代碼就是比較兩者權限了,有交集,則放行。
5、令牌無效或者過期時定制結果
在第4步,如果令牌失效或者過期,則會直接返回,這里需要定制提示信息。
新建一個RequestAuthenticationEntryPoint,實現ServerAuthenticationEntryPoint,代碼如下:

6、無權限時定制結果
在第4步鑒權的過程中,如果無該權限,也是會直接返回,這里也需要定制提示信息。
新建一個RequestAccessDeniedHandler,實現ServerAccessDeniedHandler,代碼如下:

7、OAuth2.0相關配置
經過上述6個步驟,相關組件已經准備就緒,現在直接配置到OAuth2.0中。
新建SecurityConfig這個配置類,標注注解 @EnableWebFluxSecurity,注意不是 @EnableWebSecurity,因為Spring Cloud Gateway是基於Flux實現的。詳細代碼如下:

需要配置的內容如下:
- 認證過濾器,其中利用了認證管理器對令牌的校驗
- 鑒權管理器、令牌失效異常處理、無權限訪問異常處理
- 白名單配置
- 跨域過濾器的配置
8、全局過濾器定制
試想一下:網關層面認證鑒權成功后,下游微服務如何獲取到當前用戶的詳細信息?
陳某這里是將令牌攜帶的用戶信息解析出來,封裝成JSON數據,然后通過Base64加密,放入到請求頭中,轉發給下游微服務。
這樣一來,下游微服務只需要解密請求頭中的JSON數據,即可獲取用戶的詳細信息。
因此需要在網關中定義一個全局過濾器,用來攔截請求,解析令牌,關鍵代碼如下:

上述代碼邏輯如下:
- 檢查是否是白名單,白名單直接放行
- 檢驗令牌是否存在
- 解析令牌中的用戶信息
- 封裝用戶信息到JSON數據中
- 加密JSON數據
- 將加密后的JSON數據放入到請求頭中
好了,經過上述8個步驟,完整的網關已經搭建成功了。
案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
訂單微服務搭建
由於在網關層面已經做了鑒權了(細化到每個URI),因此微服務就不用集成Spring Security單獨做權限控制了。
因此這里的微服務也是相對比較簡單了,只需要將網關層傳遞的加密用戶信息解密出來,放入到Request中,這樣微服務就能隨時獲取到用戶的信息了。
新建一個oauth2-cloud-order-service模塊,目錄如下:

新建一個過濾器AuthenticationFilter,用於解密網關傳遞的用戶數據,代碼如下:

新建兩個接口,返回當前登錄的用戶信息,如下:

注意:以上兩個接口所需要的權限已經放入到了Redis中,權限如下:
- /order/login/info:ROLE_admin和ROLE_user都能訪問
- /order/login/admin:ROLE_admin權限才能訪問

案例源碼已經上傳GitHub,關注公號:碼猿技術專欄,回復關鍵詞:9529 獲取!
為什么要將URI和權限放入Redis?
在網關的鑒權管理器那里是直接從Redis中獲取URI對應的權限,然后和令牌中的權限比較,為什么要這樣做?
這也是目前企業中比較常用的一種方式,將鑒權完全放在了網關層面,也實現了動態權限校驗。當然有些是直接將接口的權限控制在每個微服務中。
采用陳某的這種方案需要另外維護URI和權限的對應關系,當然這種難度很低,便於實現。
只是一種方案,具體是否選用還要考慮到架構層面。
測試
同時啟動上述三個服務,如下:

1、用密碼模式登錄user,獲取令牌,如下:

2、使用user用戶的令牌訪問/order/login/info接口,如下:

可以看到成功返回了,因為具備ROLE_user權限。
3、使用user用戶的令牌訪問/order/login/admin接口,如下:

可以看到直接返回了無權限訪問,直接在網關層被攔截了。
總結
本篇文章只是簡單的整合了網關+OAuth2.0,實際開發中還有一些細節待完善,由於文章篇幅限制,后續介紹......
