妹子始終沒搞懂OAuth2.0,今天整合Spring Cloud Security 一次說明白!

bucaichenmou發表於2021-12-02

大家好,我是不才陳某~

週二發了Spring Security 系列第一篇文章,有妹子留言說看了很多文章,始終沒明白OAuth2.0,這次陳某花了兩天時間,整理了OAuth2.0相關的知識,結合認證授權服務+資源服務,一次性給大家嘮明白!

這是《Spring Security 進階》2篇文章,往期文章如下:

本篇文章介紹一下OAuth2.0相關的知識點,並且手把手帶大家搭建一個認證授權中心資源服務進行OAuth2.0四種授權模式的驗證,案例原始碼詳細,一梭子帶大家瞭解清楚。

本篇文章的案例原始碼專案架構為:Spring Boot + Spring Cloud Alibaba + Spring Security ,Spring Cloud Alibaba有不瞭解可以看下陳某的往期《Spring Cloud 進階》文章:

文章目錄如下:

為什麼需要OAuth2.0?

編碼永遠都是為了解決生產中的問題,想要理解為什麼需要OAuth2,當然要從實際生活出發。

舉個例子:小區的業主點了一份外賣,但是小區的門禁系統不給外賣人員進入,此時想要外賣員進入只能業主下來開門或者告知門禁的密碼。

密碼告知外賣員豈不是每次都能憑密碼進入小區了,這明顯造成了安全隱患

那麼有沒有一種方案:既能不洩露密碼,也能讓外賣小哥進入呢?

於是此時就想到了一個授權機制,分為以下幾個步驟:

  1. 門禁系統中新增一個授權按鈕,外賣小哥只需要點選授權按鈕呼叫對應業主
  2. 業主收到小哥的呼叫,知道小哥正在要求授權,於是做出了應答授權
  3. 此時門禁系統彈出一個密碼(類似於access_token),有效期30分鐘,在30分鐘內,小哥可以憑藉這個密碼進入小區。
  4. 小哥輸入密碼進入小區

另外這個授權的密碼不僅可以通過門禁,還可以通過樓下的門禁,這就非常類似於閘道器微服務了。

令牌和密碼的區別?

上述例子中令牌和密碼的作用是一樣的,都可以進入小區,但是存在以下幾點差異:

  1. 時效不同:令牌一般都是存在過期時間的,比如30分鐘後失效,這個是無法修改的,除非重新申請授權;而密碼一般都是永久的,除非主人去修改
  2. 許可權不同:令牌的許可權是有限的,比如上述例子中,小哥獲取了令牌,能夠開啟小區的門禁、業主所在的樓下門禁,但是可能無法開啟其它幢的門禁;
  3. 令牌可以撤銷:業主可以撤銷這個令牌的授權,一旦撤銷了,這個令牌也就失效了,無法使用;但是密碼一般不允許撤銷。

什麼是OAuth2?

OAuth 是一個開放標準,該標準允許使用者讓第三方應用訪問該使用者在某一網站上儲存的私密資源(如頭像、照片、視訊等),而在這個過程中無需將使用者名稱密碼提供給第三方應用。實現這一功能是通過提供一個令牌(token),而不是使用者名稱和密碼來訪問他們存放在特定服務提供者的資料。

採用令牌(token)的方式可以讓使用者靈活的對第三方應用授權或者收回許可權。

OAuth2 是 OAuth 協議的下一版本,但不向下相容 OAuth 1.0

傳統的 Web 開發登入認證一般都是基於 session 的,但是在前後端分離的架構中繼續使用 session 就會有許多不便,因為移動端(Android、iOS、微信小程式等)要麼不支援 cookie(微信小程式),要麼使用非常不便,對於這些問題,使用 OAuth2 認證都能解決。

對於大家而言,我們在網際網路應用中最常見的 OAuth2 應該就是各種第三方登入了,例如 QQ 授權登入微信授權登入微博授權登入GitHub 授權登入等等。

OAuth2.0的四種模式?

OAuth2.0協議一共支援 4 種不同的授權模式:

  1. 授權碼模式:常見的第三方平臺登入功能基本都是使用這種模式。
  2. 簡化模式:簡化模式是不需要客戶端伺服器參與,直接在瀏覽器中向授權伺服器申請令牌(token),一般如果網站是純靜態頁面則可以採用這種方式。
  3. 密碼模式:密碼模式是使用者把使用者名稱密碼直接告訴客戶端,客戶端使用說這些資訊向授權伺服器申請令牌(token)。這需要使用者對客戶端高度信任,例如客戶端應用和服務提供商就是同一家公司,自己做前後端分離登入就可以採用這種模式。
  4. 客戶端模式:客戶端模式是指客戶端使用自己的名義而不是使用者的名義向服務提供者申請授權,嚴格來說,客戶端模式並不能算作 OAuth 協議要解決的問題的一種解決方案,但是,對於開發者而言,在一些前後端分離應用或者為移動端提供的認證授權伺服器上使用這種模式還是非常方便的。

1、授權碼模式

這種方式是最常用的流程,安全性也最高,它適用於那些有後端的 Web 應用。授權碼通過前端傳送,令牌則是儲存在後端,而且所有與資源伺服器的通訊都在後端完成。這樣的前後端分離,可以避免令牌洩漏。

令牌獲取的流程如下:

授權碼模式

上圖中涉及到兩個角色,分別是客戶端、認證中心,客戶端負責拿令牌,認證中心負責發放令牌。

但是不是所有客戶端都有許可權請求令牌的,需要事先在認證中心申請,比如微信並不是所有網站都能直接接入,而是要去微信後臺開通這個許可權。

至少要提前向認證中心申請的幾個引數如下:

  1. client_id:客戶端唯一id,認證中心頒發的唯一標識
  2. client_secret:客戶端的祕鑰,相當於密碼
  3. scope:客戶端的許可權
  4. redirect_uri:授權碼模式使用的跳轉uri,需要事先告知認證中心。

1、請求授權碼

客戶端需要向認證中心拿到授權碼,比如第三方登入使用微信,掃一掃登入那一步就是向微信的認證中心獲取授權碼。

請求的url如下:

/oauth/authorize?client_id=&response_type=code&scope=&redirect_uri=

上述這個url中攜帶的幾個引數如下:

  • client_id:客戶端的id,這個由認證中心分配,並不是所有的客戶端都能隨意接入認證中心
  • response_type:固定值為code,表示要求返回授權碼。
  • scope:表示要求的授權範圍,客戶端的許可權
  • redirect_uri:跳轉的uri,認證中心同意或者拒絕授權跳轉的地址,如果同意會在uri後面攜帶一個code=xxx,這就是授權碼

2、返回授權碼

第1步請求之後,認證中心會要求登入、是否同意授權,使用者同意授權之後直接跳轉到redirect_uri(這個需要事先在認證中心申請配置),授權碼會攜帶在這個地址後面,如下:

http://xxxx?code=NMoj5y

上述連結中的NMoj5y就是授權碼了。

3、請求令牌

客戶端拿到授權碼之後,直接攜帶授權碼傳送請求給認證中心獲取令牌,請求的url如下:

/oauth/token?
 client_id=&
 client_secret=&
 grant_type=authorization_code&
 code=NMoj5y&
 redirect_uri=

相同的引數同上,不同引數解析如下:

  • grant_type:授權型別,授權碼固定的值為authorization_code
  • code:這個就是上一步獲取的授權碼

4、返回令牌

認證中心收到令牌請求之後,通過之後,會返回一段JSON資料,其中包含了令牌access_token,如下:

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101
}

access_token則是頒發的令牌,refresh_token是重新整理令牌,一旦令牌失效則攜帶這個令牌進行重新整理。

2、簡化模式

這種模式不常用,主要針對那些無後臺的系統,直接通過web跳轉授權,流程如下圖:

簡化模式

這種方式把令牌直接傳給前端,是很不安全的。因此,只能用於一些安全要求不高的場景,並且令牌的有效期必須非常短,通常就是會話期間(session)有效,瀏覽器關掉,令牌就失效了。

1、請求令牌

客戶端直接請求令牌,請求的url如下:

/oauth/authorize?
  response_type=token&
  client_id=CLIENT_ID&
  redirect_uri=CALLBACK_URL&
  scope=

這個url正是授權碼模式中獲取授權碼的url,各個引數解析如下:

  • client_id:客戶端的唯一Id
  • response_type:簡化模式的固定值為token
  • scope:客戶端的許可權
  • redirect_uri:跳轉的uri,這裡後面攜帶的直接是令牌,不是授權碼了。

2、返回令牌

認證中心認證通過後,會跳轉到redirect_uri,並且後面攜帶著令牌,連結如下:

https://xxxx#token=NPmdj5

#token=NPmdj5這一段後面攜帶的就是認證中心攜帶的,令牌為NPmdj5

3、密碼模式

密碼模式也很簡單,直接通過使用者名稱密碼獲取令牌,流程如下:

密碼模式

1、請求令牌

認證中心要求客戶端輸入使用者名稱、密碼,認證成功則頒發令牌,請求的url如下:

/oauth/token?
  grant_type=password&
  username=&
  password=&
  client_id=&
  client_secret=

引數解析如下:

  • grant_type:授權型別,密碼模式固定值為password
  • username:使用者名稱
  • password:密碼
  • client_id:客戶端id
  • client_secret:客戶端的祕鑰

2、返回令牌

上述認證通過,直接返回JSON資料,不需要跳轉,如下:

{    
  "access_token":"ACCESS_TOKEN",
  "token_type":"bearer",
  "expires_in":2592000,
  "refresh_token":"REFRESH_TOKEN",
  "scope":"read",
  "uid":100101
}

access_token則是頒發的令牌,refresh_token是重新整理令牌,一旦令牌失效則攜帶這個令牌進行重新整理。

4、客戶端模式

適用於沒有前端的命令列應用,即在命令列下請求令牌。

這種方式給出的令牌,是針對第三方應用的,而不是針對使用者的,即有可能多個使用者共享同一個令牌。

流程如下:

客戶端模式

1、請求令牌

請求的url為如下:

/oauth/token?
grant_type=client_credentials&
client_id=&
client_secret=

引數解析如下:

  • grant_type:授權型別,客戶端模式固定值為client_credentials
  • client_id:客戶端id
  • client_secret:客戶端祕鑰

2、返回令牌

認證成功後直接返回令牌,格式為JSON資料,如下:

{
    "access_token": "ACCESS_TOKEN",
    "token_type": "bearer",
    "expires_in": 7200,
    "scope": "all"
}

OAuth2.0的認證中心搭建

為了方便測試OAuth2的四種授權模式,這裡為了方便測試,簡單搭建一個認證中心,後續會逐漸完善。

1、案例架構

陳某使用的是Spring Boot + Spring Cloud Alibaba 作為基礎搭建,新建一個oauth2-auth-server-in-memory模組作為認證中心,目錄如下:

案例原始碼已經上傳GitHub,關注公號:碼猿技術專欄,回覆關鍵詞 9529 獲取。

2、新增依賴

Spring Boot 和 Spring Cloud 的相關依賴這裡陳某就不再說了,直接上Spring Security和OAuth2的依賴,如下:

<!--spring security的依賴-->
<dependency>
   <groupId>org.springframework.boot</groupId>
	<artifactId>spring-boot-starter-security</artifactId>
</dependency>

<!--OAuth2的依賴-->
<dependency>
	<groupId>org.springframework.cloud</groupId>
	<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>

3、Spring Security安全配置

這裡主要涉及到Spring Security的配置,有不清楚的可以陳某第一篇文章:實戰!Spring Boot Security+JWT前後端分離架構登入認證!

SecurityConfig這個配置類中主要設定有4塊內容,如下:

1、加密方式

採用BCryptPasswordEncoder加密,如下:

2、配置使用者

這裡為了方便測試,直接將使用者資訊儲存在記憶體中,後續完善,程式碼如下:

上述程式碼配置了兩個使用者,如下:

  • 使用者名稱admin,密碼123,角色admin
  • 使用者名稱user,密碼123,角色user

3、注入認證管理器AuthenticationManager

AuthenticationManager密碼授權模式下會用到,這裡提前注入,如果你用的不是密碼模式,可以不注入,程式碼如下:

4、配置安全攔截策略

由於需要驗證授權碼模式,因此開啟表單提交模式,所有url都需要認證,程式碼如下:

4、令牌儲存策略配置

令牌支援多種方式儲存,比如記憶體方式、RedisJWT,比較常用的兩種則是Redis、JWT。

這裡暫時使用記憶體儲存的方式,一旦伺服器重啟令牌將會失效。

程式碼如下:

5、OAuth2.0的配置類

不是所有配置類都可以作為OAuth2.0認證中心的配置類,需要滿足以下兩點:

  1. 繼承AuthorizationServerConfigurerAdapter
  2. 標註 @EnableAuthorizationServer 註解

程式碼如下:

AuthorizationServerConfigurerAdapter需要實現的三個方法如下:

下面便是圍繞這三個方法進行OAuth2的詳細配置。

6、客戶端配置

在介紹OAuth2.0 協議的時候介紹到,並不是所有的客戶端都有許可權向認證中心申請令牌的,首先認證中心要知道你是誰,你有什麼資格?

因此一些必要的配置是要認證中心分配給你的,比如客戶端唯一Id祕鑰許可權

客戶端配置的儲存也支援多種方式,比如記憶體資料庫,對應的介面為:org.springframework.security.oauth2.provider.ClientDetailsService,介面如下:

同樣這裡為了方便測試,依然是載入在記憶體中,後續完善,完整的配置如下:

幾個重要引數說一下,如下:

  • .withClient("myjszl"):指定客戶端唯一ID為myjszl
  • .secret():指定祕鑰,使用加密演算法加密了,祕鑰為123
  • .resourceIds("res1"):給客戶端分配的資源許可權,對應的是資源服務,比如訂單這個微服務就可以看成一個資源,作為客戶端肯定不是所有資源都能訪問。
  • authorizedGrantTypes():定義認證中心支援的授權型別,總共支援五種
    • 授權碼模式:authorization_code
    • 密碼模式:password
    • 客戶端模式:client_credentials
    • 簡化模式:implicit
    • 令牌重新整理:refresh_token,這並不是OAuth2的模式,定義這個表示認證中心支援令牌重新整理
  • scopes():定義客戶端的許可權,這裡只是一個標識,資源服務可以根據這個許可權進行鑑權。
  • autoApprove:是否需要授權,設定為false則不需要使用者點選確認授權直接返回授權碼
  • redirectUris:跳轉的uri

7、授權碼服務配置

使用授權碼模式必須配置一個授權碼服務,用來頒佈和刪除授權碼,當然授權碼也支援多種方式儲存,比如記憶體,資料庫,這裡暫時使用記憶體方式儲存,程式碼如下:

8、令牌服務的配置

除了令牌的儲存策略需要配置,還需要配置令牌的服務AuthorizationServerTokenServices用來建立、獲取、重新整理令牌,程式碼如下:

9、令牌訪問端點的配置

目前這裡僅僅配置了四個,分別如下:

  • 配置了授權碼模式所需要的服務,AuthorizationCodeServices
  • 配置了密碼模式所需要的AuthenticationManager
  • 配置了令牌管理服務,AuthorizationServerTokenServices
  • 配置/oauth/token申請令牌的uri只允許POST提交。

詳細程式碼如下:

spring Security框架預設的訪問端點有如下6個:

  • /oauth/authorize:獲取授權碼的端點
  • /oauth/token:獲取令牌端點。
  • /oauth/confifirm_access:使用者確認授權提交端點。
  • /oauth/error:授權服務錯誤資訊端點。
  • /oauth/check_token:用於資源服務訪問的令牌解析端點。
  • /oauth/token_key:提供公有密匙的端點,如果你使用JWT令牌的話。

當然如果業務要求需要改變這些預設的端點的url,也是可以修改的,AuthorizationServerEndpointsConfigurer有一個方法,如下:

public AuthorizationServerEndpointsConfigurer pathMapping(String defaultPath, String customPath)

第一個引數:需要替換的預設端點url

第二個引數:自定義的端點url

10、令牌訪問安全約束配置

主要對一些端點的許可權進行配置,程式碼如下:

OAuth2.0的資源服務搭建

客戶端申請令牌的目的就是為了訪問資源,當然這個資源也是分許可權的,一個令牌不是所有資源都能訪問的。

在認證中心搭建的第6步配置客戶端詳情的時候,一行程式碼.resourceIds("res1")則指定了能夠訪問的資源,可以配置多個,這裡的res1則是唯一對應一個資源。

1、案例架構

陳某使用的是Spring Boot + Spring Cloud Alibaba 作為基礎搭建,新建一個oauth2-auth-resource-in-memory模組作為認證中心,目錄如下:

案例原始碼已經上傳GitHub,關注公號:碼猿技術專欄,回覆關鍵詞 9529 獲取。

2、OAuth2.0的配置類

作為資源服務的配置類必須滿足兩個條件,如下:

  • 標註註解@EnableResourceServer
  • 繼承ResourceServerConfigurerAdapter

程式碼如下:

3、令牌校驗服務配置

由於認證中心使用的令牌儲存策略是在記憶體中的,因此服務端必須遠端呼叫認證中心的校驗令牌端點/oauth/check_token進行校驗。

程式碼如下:

注意:遠端校驗令牌存在效能問題,但是後續使用JWT令牌則本地即可進行校驗,不必遠端校驗了。

4、配置客戶端唯一id和令牌校驗服務

上文說到客戶端有一個唯一標識,因此需要配置上,程式碼如下:

5、配置security的安全機制

上文在認證中心的第6步配置客戶端詳情那裡,有一行程式碼.scopes("all")則是指定了客戶端的許可權,資源服務可以根據這個scope進行url的攔截。

攔截方式如下:

.access("#oauth2.hasScope('')")

詳細配置程式碼如下:

這裡陳某配置了所有路徑都需要all的許可權。

6、新建測試介面

新建了兩個介面,如下:

  • /hello:認證成功都可以訪問
  • /admin:只有具備ROLE_admin角色的使用者才可以訪問

OAuth2.0的四種模式測試

下面結合認證中心、資源服務對OAuth2.0的四種服務進行測試。

啟動上述搭建的認證中心和資源服務,如下圖:

授權碼模式

1、獲取授權碼

請求的url如下:

http://localhost:2003/auth-server/oauth/authorize?client_id=myjszl&response_type=code&scope=all&redirect_uri=http://www.baidu.com

瀏覽器訪問,security需要登入,如下:

輸入使用者名稱user,密碼123,成功登入。

此時來到了確認授權的頁面,如下:

選擇Apporove、確認授權,成功跳轉到了百度頁面,並且攜帶了授權碼,如下:

這裡的6yV2bF就是獲取到的授權碼。

2、獲取token

http://localhost:2003/auth-server/oauth/token?code=jvMH5U&client_id=myjszl&client_secret=123&redirect_uri=http://www.baidu.com&grant_type=authorization_code

注意/oauth/token獲取token的介面請求允許的方式要配置在授權伺服器中,比如配置POST方式,程式碼如下:

.allowedTokenEndpointRequestMethods(HttpMethod.POST)

POSTMAN請求如下圖:

3、訪問資源服務

拿著令牌訪問資源服務的/hello介面,請求如下:

請求頭需要新增Authorization,並且值為Bearer+" "+access_token的形式。

注意:Bearer後面一定要跟一個空格。

密碼模式

密碼模式比較簡單,不用先獲取授權碼,直接使用使用者名稱密碼獲取token。

POSTMAN請求如下:

PS:訪問資源自己拿著獲取到的令牌嘗試下.....

簡化模式

簡化模式就很簡單了,拿著客戶端id就可以獲取token,請求的url如下:

http://localhost:2003/auth-server/oauth/authorize?response_type=token&client_id=myjszl&redirect_uri=http://www.baidu.com&scope=all

這個過程和獲取授權碼一樣,需要登入同意授權

最終跳轉到百度,連結後面直接攜帶了令牌,如下:

上圖中的0d5ecf06-b255-4272-b0fa-8e51dde2ce3e則是獲取的令牌。

PS:訪問資源自己嘗試下..........

客戶端模式

請求的url如下:

http://localhost:2003/auth-server/oauth/token?client_id=myjszl&client_secret=123&grant_type=client_credentials

POSTMAN請求如下:

PS:訪問資源自己嘗試下..........

OAuth2.0 其他端點的測試

Spring Security OAuth2.0還提供了其他的端點,下面來逐一測試一下。

1、重新整理令牌

OAuth2.0提供了令牌重新整理機制,一旦access_token過期,客戶端可以拿著refresh_token去請求認證中心進行令牌的續期。

請求的url如下:

http://localhost:2003/auth-server/oauth/token?client_id=myjszl&client_secret=123&grant_type=refresh_token&refresh_token=

POSTMAN請求如下:

2、校驗令牌

OAuth2.0還提供了校驗令牌的端點,請求的url如下:

http://localhost:2003/auth-server/oauth/check_token?toke=

POSTMAN請求如下:

總結

本文介紹了OAuth2.0協議原理、四種授權模式,並且搭建了認證授權中心、資源服務進行了四種模式的測試。

作為OAuth2.0入門教程已經非常詳細了...........

最後說一句(別白嫖,求關注)

陳某每一篇文章都是精心輸出,已經寫了3個專欄,整理成PDF,獲取方式如下:

  1. 《Spring Cloud 進階》PDF:關注公號:【碼猿技術專欄】回覆關鍵詞 Spring Cloud 進階 獲取!
  2. 《Spring Boot 進階》PDF:關注公號:【碼猿技術專欄】回覆關鍵詞 Spring Boot進階 獲取!
  3. 《Mybatis 進階》PDF:關注公號:【碼猿技術專欄】回覆關鍵詞 Mybatis 進階 獲取!

如果這篇文章對你有所幫助,或者有所啟發的話,幫忙點贊在看轉發收藏,你的支援就是我堅持下去的最大動力!

相關文章