基於spring-security-oauth2搭建授權服務器
背景:
-
需要API網關控制權限,單點登陸。
-
做前后端分離的應用,前端使用vue+elementui實現。
當前關於這方面的系統資料較少,因此大多是找尋網上零散的示例解析,結合官方文檔中的demo再加上源碼跟蹤調試來進行學習與搭建。但由於涉及的知識點較多,且零散示例中配置或實現方式各有不同,作者經常只會記錄關鍵、核心部分內容,因此會漏掉一些基礎配置信息,給初學者帶來極大困難。往往按照多個示例拼湊出來的demo無法正常運行,或者不明就里。官方給出的文檔又需要具備一定英文功底。綜合以上因素,這篇文章橫空出世:)
既包含周邊理論知識淺析,又包含實際案例demo完整代碼(為了保證下載即可正常運行已附上SQL語句)
涉及知識點:
- Oauht2基礎知識(授權類型,運行流程)
- 服務器證書,Keytool工具基本使用(常用命令參數解析及證書類別)
- SpringBoot項目增加https支持(https協議簡單概述)
- Springsecurity知識(spring-security-oauth2是基於springsecurit的,很多配置相關都是沿用springsecurity的)
- 具體項目實現(包含服務端及客戶端具體實現,鑒權、SSO)
Oauht2基礎知識
既然是要搭建基於spring-security-oauth2的授權服務器,那Oauth2的基礎原理和運行流程我們還是需要了解一下的,否則對於授權模式選擇和認證授權流程會比較暈。對於Oauth2的介紹這里強烈推薦: 理解OAuth2.0,在這里只簡單描述和摘抄部分關鍵信息.
核心角色介紹
-
三方客戶端
-
資源所有者
-
認證服務器
-
資源服務器
為了便於理解,這里舉一個大家耳熟能詳的應用場景來介紹各個角色在實際應用中是如何交互的:在一個愉快的周六午后,小明同學吃完午飯就迫不及待的躺在床上,拿起手機打開吃雞游戲(三方客戶端),為了便於同朋友開黑,小明選擇了微信登陸的方式,這時候吃雞游戲(三方客戶端)跳轉到一個微信登陸認證頁面(認證服務器),在這里小明(資源所有者)輸入賬號密碼(身份認證),登陸成功后界面上顯示:是否授權使吃雞游戲可以訪問你的微信頭像、昵稱、好友資料等。(授權)。授權通過后,剩下是后台處理,用戶不可見(返回給客戶端一個授權碼,客戶端拿到授權碼結合開始申請授權時的APPKEY申請TOKEN,通過后客戶端憑着這個TOKEN去資源服務器獲取用戶頭像、昵稱等信息)
客戶端的授權模式
-
授權碼模式(authorization code)
-
簡化模式(implicit)
-
密碼模式(resource owner password credentials)
-
客戶端模式(client credentials)
關於這4種授權模式的區別,詳細內容請參見[OAuth2.0](http://www.ruanyifeng.com/blog/2014/05/oauth_2_0.html "OAuth2.0"),在這里我給出了自己比較土的解釋:
授權碼模式:安全性最高,也是比較常用的方式,但是整個流程最長。運行流程見上面舉的“吃雞”游戲登陸授權例子。
簡化模式: 跳過了獲取授權碼的環節,后續流程同授權碼模式。
密碼模式:把賬號密碼給到客戶端程序,由客戶端程序去請求認證服務器獲取token,這種方式客戶端是能夠獲取到用戶名、密碼信息的。
客戶端模式:這種其實就和用戶授權沒什么關系,需要認證的主體是客戶端本身了,客戶端本身以自己的名義去找認證服務器要授權。
服務器證書,Keytool工具基本使用
涉及到鑒權,當然少不了https的應用。在使用https前,需要創建服務器證書,關於證書這部分的詳細內容請參見:[HTTPS與SSL證書概要](http://www.runoob.com/w3cnote/https-ssl-intro.html "HTTPS與SSL證書概要")
本文中,選擇使用keytool工具來自己動手生成一個。由於springboot默認只支持jks和p12格式的。在此我們選擇生成jks格式的證書,命令如下(需要注意的是得用管理員權限打開cmd命令行,否則生成keystore文件會失敗,報filenotfound錯誤)
- 選用JKS的證書,生成語句:
keytool -genkeypair -alias server -keyalg RSA -validity 3650 -keystore server.jks
其中需要特別注意到的是,在輸入以上命令后,會要求你寫名稱、區域等信息,如下圖
這里的姓氏、名稱是你的域名,如果域名與證書內容不符,訪問時將報SSLHandshakeException: 由此可知是ssl 握手失敗!的錯誤.
- 導出cer證書(給到第三步導入用的)
keytool -export -alias server -keystore server.jks -rfc -file server.cer - 導入到jdk中
keytool -import -alias server -file server.cer -keystore cacerts
生成文件如下:
踩坑爬坑
Bad Request This combination of host and port requires TLS
使用 http://localhost:8081/oauth/authorize?client_id=erpmanager&response_type=code&redirect_uri=www.forever24.cn 獲取code碼時返回報錯信息
解決方案:返回信息告訴我們,表示需要通過https來訪問,用https://localhost:8081/oauth/authorize?client_id=testcode&response_type=code&redirect_uri=www.forever24.cn訪問結果如下:
選擇信任后,轉到登陸頁面如下,這個登陸頁面可以直接替換成自己的(需要自己寫登陸頁面,否則默認就是這個簡陋的)
client信息讀取有in-memory,jdbc等多種方式,這里采用的是jdbc方式,簡單來說就是自己從數據庫中查詢看然后跟用戶輸入的賬號密碼比對,需要自己重寫UserService部分
輸入賬號密碼后,滿懷期待😊 ,but:又報錯了,奶奶的熊,明明輸入的賬號密碼是正確的為什么還報 Handling ClientRegistrationException error: No client with requested id: erpmanager 的錯呢?
而且,自己寫的 SupplierInfo supplierInfo = supplierManRepository.findByuserName(username); 查詢user信息中,明明用戶名密碼填寫是對的,也有erpmanager這個用戶 根據報錯信息,顯示的是2019-01-10 11:22:15.974 INFO 9444 --- [nio-8081-exec-4] o.s.s.o.p.e.AuthorizationEndpoint : Handling ClientRegistrationException error: No client with requested id: erpmanager
根據關鍵字搜索可以找到,報錯信息是從org.springframework.security.oauth2.provider.endpoint.AuthorizationEndpoint
報出來的,並且訪問的是oauth/authorize
,可以找到這個url對應的處理方法。
獲取code碼,身份驗證的代碼在:
獲取token的入口代碼在:
Full authentication is required to access this resourceunauthorized
數據庫表說明(使用spring-oauth-server的系統表):http://andaily.com/spring-oauth-server/db_table_description.html
關於SecurityConfig 的配置,需要先了解 構造器模式,否則比較難以理解。可以參見 https://www.cnblogs.com/iCanhua/p/8636085.html
@Override
protected void configure(HttpSecurity http) throws Exception { // @formatter:off
http.requestMatchers().antMatchers("/login", "/oauth/authorize")
.and().authorizeRequests().anyRequest().authenticated()
.and().formLogin().permitAll();
} // @formatter:on
豁然開朗,查下數據庫的[oauth_client_details]表,發現根本沒有設置 erpmanager 這個用戶。