第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務

恆宇少年發表於2017-11-15

OAuth是一個關於授權的開放網路標準,在全世界得到的廣泛的應用,目前是2.0的版本。OAuth2在“客戶端”與“服務提供商”之間,設定了一個授權層(authorization layer)。“客戶端”不能直接登入“服務提供商”,只能登入授權層,以此將使用者與客戶端分離。“客戶端”登入需要OAuth提供的令牌,否則將提示認證失敗而導致客戶端無法訪問服務。下面我們就來講解下SpringBoot專案中是如何配置使用OAuth2伺服器端,並讓OAuth2整合SpringSecurity來保護我們的REST介面。


知識星球

已開通知識星球,歡迎大家加入交流技術以及提問SpringBoot相關問題。

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務

本章目標

基於SpringBoot專案提供一個繼承OAuth2安全框架的REST API服務端,必須獲取訪問授權令牌後才可以訪問資源。

OAuth2授權方式

我們在文章開始已經說過了,我們的保護資源必須通過授權得到的令牌才可以訪問。那麼我們這個授權令牌要通過什麼方式獲取呢?

OAuth2為我們提供了四種授權方式:

1、授權碼模式(authorization code)
2、簡化模式(implicit)
3、密碼模式(resource owner password credentials)
4、客戶端模式(client credentials)

授權碼模式

授權碼相對其他三種來說是功能比較完整、流程最安全嚴謹的授權方式,通過客戶端的後臺伺服器與服務提供商的認證伺服器互動來完成。流程如下圖2所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖2

簡化模式

這種模式不通過伺服器端程式來完成,直接由瀏覽器傳送請求獲取令牌,令牌是完全暴露在瀏覽器中的,這種模式極力不推崇。流程如下圖3所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖3

密碼模式

密碼模式也是比較常用到的一種,客戶端向授權伺服器提供使用者名稱、密碼然後得到授權令牌。這種模式不過有種弊端,我們的客戶端需要儲存使用者輸入的密碼,但是對於使用者來說信任度不高的平臺是不可能讓他們輸入密碼的。流程如下圖4所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖4

客戶端模式

客戶端模式是客戶端以自己的名義去授權伺服器申請授權令牌,並不是完全意義上的授權。如下圖5所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖5

上述簡單的介紹了OAuth2內部的四種授權方式,我們下面使用密碼模式來進行測試,並且我們使用資料庫中的使用者資料來做驗證處理,下面我們先來構建專案。

構建專案

我們使用IndeiiJ IDEA工具來構建一個SpringBoot專案,目前最新版本的是1.5.3,應該是昨天剛正式釋出。專案我們預先引入幾個模組,Web、JPA、MySQL、Security、SpringSecurityOAuth2、Druid等,專案結構如下圖6所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖6

專案構建完成後我們要配置資料庫表結構,因為我們要是資料庫內儲存AccessToken以及RefershToken還有我們的SpringSecurity使用者驗證資訊以及使用者角色資訊等。

配置資料庫

安全使用者資訊表

使用者資訊表包含了簡單的登入名、密碼、郵箱、狀態等。表結構如下圖7所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖7

安全形色資訊表

角色資訊表結構如下圖8所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖8

使用者角色關聯表

使用者與角色關聯表結構如下圖9所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖9

AccessToken資訊表

我們使用的是SpringSecurityOAuth2提供的Jdbc方式進行操作Token,所以需要根據標準建立對應的表結構,access_token資訊表結構如下圖10所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖10

RefreshToken資訊表

重新整理Token時需要用到refresh_token資訊表結構如下圖11所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖11

我們的資料庫表結構已經建完了,下面我們只需要建立使用者資訊、角色資訊的實體即可,因為OAuth2內部運算元據庫使用的JdbcTemplate我們只需要傳入一個DataSource物件就可以了,實體並不需要配置。

建立使用者實體

使用者實體如下圖12所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖12

建立角色實體

角色實體如下圖13所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖13

使用者實體以及角色實體是用來配置SpringSecurity時用到的實體,我們配置SpringSecurity時需要使用SpringDataJPA從資料庫中讀取資料,下我們來配置UserJPA以及AuthorityJPA。

UserJPA

配置訪問資料庫獲取使用者資訊,程式碼如下圖14所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖14

我們在UserJPA內新增了一個自定義查詢,使用了HQL語法來構建的語句,根據使用者名稱不區分大小寫進行查詢。

Application.yml配置檔案

我們從之前的專案中第十三章:SpringBoot實戰SpringDataJPA中原始碼複製一個application.yml配置檔案到專案resources下(注意:需要修改對應的資料庫配置),如下圖所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
.

AuthorityJPA

配置訪問資料庫中的角色列表,程式碼如下圖15所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖15

下面我們來配置兩個控制器用來區分我們配置OAuth2是否已經生效。

HelloWorldController

我在HelloWorldController內只新增一個字串的輸出,這個控制器我們開放,讓SpringSecurity不去管理,配置將會在下面展現,控制器程式碼如下圖16所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖16

SecureController

這個控制器是需要我們獲取授權Token後使用Token才可以訪問到的,程式碼如下圖17所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖17

綜上所述我們的專案基礎的構建已經完成,大家都知道SpringSecurity在使用資料庫的資料時需要自定義UserDetailsService用來從資料庫中根據使用者名稱查詢使用者資訊以及角色資訊並返回給SpringSecurity存放到記憶體中。

自定義UserDetailsService

我們建立一個名叫HengYuUserDetailsService的類並且實現UserDetailsService介面,程式碼如下圖18所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖18

我們在HengYuUserDetailsService類中做了從資料庫讀取使用者的操作,如果沒有查詢到使用者直接丟擲異常提示,如果查詢到並且設定對應的角色後返回SpringSecurity內建的User物件例項。

開啟SpringSecurity配置

下面我們來配置SpringSecurity相關的內容,我們新建立一個配置類SecurityConfiguration,程式碼如下圖19所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖19

我們在配置類中注入了上面我們自定義的HengYuUserDetailsService以及使用者密碼驗證規則,我們使用ignoring()方法排除了HelloWorldController內的公開方法,這裡可以配置萬用字元的形式排除。

配置安全資源伺服器

下面我們開始配置相關OAuth2的內容,我們建立一個OAuth2總配置類OAuth2Configuration,類內新增一個子類用於配置資源伺服器,如下圖20所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖20

我們在OAuth2Configuration配置類中新增子類ResourceServerConfiguration繼承自ResourceServerConfigurerAdapter完成資源伺服器的配置,使用@EnableResourceServer註解來開啟資源伺服器,因為整合SpringSecurity的緣故,我們需要配置登出時清空對應的access_token控制以及自定義401錯誤內容(authenticationEntryPoint),在配置類中我們排除了對/hello公開地址攔截以及/secure下的所有地址都必須授權才可以訪問。

自定義401錯誤碼內容

我們上圖已經用到了對應的類CustomAuthenticationEntryPoint,該類是用來配置如果沒有許可權訪問介面時我們返回的錯誤碼以及錯誤內容,程式碼如下圖21所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖21

定義登出控制

當我們退出系統時需要訪問SpringSecrutiy的logout方法來清空對應的session資訊,那我們退出後改使用者的access_token還依然存在那就危險了,一旦別人知道該token就可以使用之前登入使用者的許可權來操作業務。logout控制程式碼如下圖22所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖22

開啟OAuth2驗證伺服器

我們還是在OAuth2Configuration配置類中新增一個子類,用於開啟OAuth2的驗證伺服器,程式碼如下圖23、24所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖23

圖23中我們建立了一個名叫AuthorizationServerConfiguration的類繼承自AuthorizationServerConfigurerAdapter並且實現了EnvironmentAware(讀取properties檔案需要)介面,並使用@EnableAuthorizationServer註解開啟了驗證伺服器,可以看到我們使用SpringSecurityOAuth2內定義的JdbcStore來運算元據庫中的Token,當然需要有需要我們可以通過SpringDataJPA自定義Sotre

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖24

圖24中我們的OAuth2的客戶端配置並沒有從資料庫中讀取而是使用了記憶體中獲取,因為本章的內容比較多,所以在後期文章中我們會再次講到如何從資料庫中獲取clients進行驗證。我們在建立客戶端資訊時使用到了application.properties配置檔案的自定義配置,具體配置內容如下圖25所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖25

執行測試

專案編寫完成,接下來我們使用SpringBootApplication形式來執行專案進行測試,執行專案時查詢控制檯輸出日誌是否正確!

我們先來使用Postman工具訪問一下我們公開的地址127.0.0.1:8080/hello,如下圖26所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖26

可以看到我們是可以正確的訪問到介面輸出內容的,下面我們再來訪問一下被oauth2管理的地址127.0.0.1:8080/secure,如下圖27所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖27

我們可以看到直接給我們返回了一個頁面,這樣就不對了,我們應該得到一個401的錯誤碼以及自定義的資訊才對,當然我們需要新增一些配置來完成這個功能,我們開啟application.properties配置檔案新增如下圖28配置:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖28

圖中畫紅色框的就是我們新新增的配置內容,這個配置的意思時,將我們的資源攔截的過濾器執行順序放到第3個執行,也就是在oauth2的認證伺服器後面執行,我們重啟下專案再來訪問下剛才的地址,輸出內容如下圖29所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖29

可以看到正如我們預期一樣,返回了401錯誤以及我們自定義的錯誤碼”Access Denied“,下面我們來獲取access_token。

獲取AccessToken

我們在獲取token之前需要在資料庫中新增幾條對應的資料,具體的SQL我會放到原始碼專案的resources目錄下,文章地址有原始碼地址。我們來訪問/oauth/token地址獲取access_token,如下圖30所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖30

可以看到我們訪問的地址,grant_type使用到了password模式,我們在上面的配置中就是配置我們的客戶端(yuqiyu_home_pc)可以執行的模式有兩種:password、refresh_token。獲取access_token需要新增客戶端的授權資訊clientid、secret,通過Postman工具的頭授權資訊即可輸出對應的值就可以完成Basic Auth的加密串生成。

成功訪問後oauth2給我們返回了幾個引數:

access_token:本地訪問獲取到的access_token,會自動寫入到資料庫中。
token_type:獲取到的access_token的授權方式
refersh_token:重新整理token時所用到的授權token
expires_in:有效期(從獲取開始計時,值秒後過期)
scope:客戶端的介面操作許可權(read:讀,write:寫)

使用AccessToken訪問

我們使用獲取到的access_token值來訪問對應的地址http://127.0.0.1:8080/secure?access_token=9ca7fd9b-1289-440b-b1a1-0303782f660e,效果如下圖31所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖31

可以看到我們已經可以正常的訪問到資料內容了,證明我們的access_token是有效的。當我們用到的token已經過期時效果如下圖32所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖32

oauth2告訴我們需要重新整理Token了,您傳入的token值已經過期了。

重新整理AccessToken

我們的access_token過期我們需要重新整理後返回新的token,使用新token才能繼續運算元據介面。重新整理access_token如下圖33所示:

第十八章:SpringBoot專案中使用SpringSecurity整合OAuth2設計專案API安全介面服務
圖33

看到上圖33紅色框內的值了嗎?這個就是我們之前獲取token時,oauth2給我們返回的refresh_token值,我們需要用到該值來進行重新整理token。新的token值得有效期可以看到又是我們配置的預設1800秒,重新整理token時oauth2還是給我們返回了一個refersh_token值,該值要作為下次重新整理token時使用。

總結

綜上內容就是本章的全部內容,本章的內容比較多希望讀者可以仔細閱讀,本章主要講解了SpringBoot作為框架基礎上配置SpringSecurity安全框架整合OAuth2安全框架做雙重安全,講解如果通過資料庫的形式獲取到授權使用者資訊以及角色列表,通過記憶體配置的OAuth2的客戶端配置來獲取access_token以及如何使用access_token訪問受保護的資源介面。

本章程式碼已經上到碼雲:

SpringBoot配套原始碼地址:gitee.com/hengboy/spr…

SpringCloud配套原始碼地址:gitee.com/hengboy/spr…

SpringBoot相關係列文章請訪問:目錄:SpringBoot學習目錄

QueryDSL相關係列文章請訪問:QueryDSL通用查詢框架學習目錄

SpringDataJPA相關係列文章請訪問:目錄:SpringDataJPA學習目錄

SpringBoot相關文章請訪問:目錄:SpringBoot學習目錄,感謝閱讀!

歡迎加入QQ技術交流群,共同進步。


相關文章