Spring OAuth2 開(kāi)發(fā)指南(一):體系架構(gòu)和開(kāi)發(fā)概覽
[TOC]
一、開(kāi)篇
《Spring OAuth2 開(kāi)發(fā)指南》是系列文章,詳細(xì)介紹基于 Spring 生態(tài)(包括 Spring Cloud) OAuth2 的實(shí)戰(zhàn)開(kāi)發(fā)。本系列將由五篇文章組成:
- (一)體系架構(gòu)和開(kāi)發(fā)概覽:是系列文章的開(kāi)篇,主要對(duì) OAuth2 的體系架構(gòu)和主要流程進(jìn)行梳理剖析,并對(duì)當(dāng)前 Spring OAuth2 開(kāi)發(fā)做一個(gè)概括性、全局性介紹;
- (二)OAuth2 密碼模式開(kāi)發(fā)實(shí)例
- (三)OAuth2 客戶端模式開(kāi)發(fā)實(shí)例
- (四)OAuth2 授權(quán)碼模式開(kāi)發(fā)實(shí)例
- (五)OAuth2 微服務(wù)場(chǎng)景實(shí)例開(kāi)發(fā):以密碼模式為例,介紹在微服務(wù)場(chǎng)景下使用 OAuth2 以及整合 JWT 的關(guān)鍵技術(shù)方法,主要圍繞客戶端、微服務(wù)網(wǎng)關(guān)、認(rèn)證授權(quán)服務(wù)進(jìn)行,不涉及微服務(wù)的其他模塊等。
本系列第一篇主要聚焦 OAuth2 體系的概念介紹,其余各篇偏向于實(shí)戰(zhàn),主要實(shí)現(xiàn) OAuth2 的三種授權(quán)模式:密碼模式、客戶端模式和授權(quán)碼模式,包括展示授權(quán)服務(wù)器、資源服務(wù)器、客戶端等幾種角色的交互,以及 JWT 的整合。并且每個(gè)實(shí)例都提供兩個(gè)代碼版本:一個(gè)是基于舊的 Spring Security OAuth2 組件;一個(gè)是基于新的 Spring Authorization Server 組件。
需要注意的是 password 模式由于 OAuth2.1 不推薦使用所以只能提供舊的組件代碼版本,具體請(qǐng)參見(jiàn) https://datatracker.ietf.org/doc/html/draft-ietf-oauth-v2-1-02
如果對(duì)認(rèn)證和授權(quán)平臺(tái)的相關(guān)理論感興趣,可以移步本人早前的兩篇文章:
- 平臺(tái)級(jí) SaaS 架構(gòu)的基礎(chǔ):統(tǒng)一身份管理系統(tǒng), http://www.itdecent.cn/p/990d8acfdb69
- 微服務(wù)架構(gòu)下的統(tǒng)一身份認(rèn)證和授權(quán), http://www.itdecent.cn/p/2571f6a4e192
二、OAuth2 體系結(jié)構(gòu)
OAuth 授權(quán)體系設(shè)計(jì)之初主要是為了解決第三方應(yīng)用登錄和授權(quán)的問(wèn)題,但由于其嚴(yán)格規(guī)范的流程定義,廣泛的授權(quán)通用性,且與具體技術(shù)平臺(tái)無(wú)關(guān)等諸多優(yōu)點(diǎn),逐漸發(fā)展成為認(rèn)證和授權(quán)領(lǐng)域的主流技術(shù)規(guī)范。但其實(shí) OAuth2 規(guī)范歸納起來(lái)并不復(fù)雜,就四種主要的授權(quán)模式和五種角色。
四種授權(quán)模式,分別是:
- 授權(quán)碼模式(authorization code)
- 簡(jiǎn)化模式(implicit)
- 密碼模式(resource owner password credentials)
- 客戶端模式(client credentials)
五種主要的角色,分別是:
- 用戶代理/瀏覽器 User Agent
- 客戶端 Client
- 資源所有者 Resource Owner
- 資源服務(wù)器 Resource Server (受保護(hù)資源)
- 授權(quán)服務(wù)器 Authorization Server
建議熟練掌握以上概念,理清其中的關(guān)系,有助于對(duì)整個(gè)體系有一個(gè)全局性的了解。倘若一上來(lái)就擼代碼,那造出來(lái)的系統(tǒng)必然不健康。須知身份管理體系是任何平臺(tái)的重要基礎(chǔ)設(shè)施,一旦建造的不牢固,必然會(huì)種下隱患,好似大廈地基埋下的一顆延遲炸彈。
回到主題,四種模式都有其特定的使用場(chǎng)景,但是在落地過(guò)程中,也可以根據(jù)實(shí)際情況自行取舍。為了方便接下來(lái)的介紹,我們假設(shè)兩套演示案例:基于圖像的物品分類系統(tǒng)(IBCS,Image-Based Classification System)、相冊(cè)預(yù)覽系統(tǒng)(PAPS,Photo Album Preview System)。
為什么假設(shè)兩套?主要是為了分析對(duì)比各種授權(quán)模式的適用場(chǎng)景。
場(chǎng)景假設(shè) A:基于圖像的物品分類系統(tǒng)(IBCS,Image-Based Classification System)
假設(shè)團(tuán)隊(duì)正在開(kāi)發(fā)一套“基于圖像的物品分類系統(tǒng)(Image-Based Classification System)”,該系統(tǒng)采用 RESTful API 對(duì)外交互,主要功能是允許用戶通過(guò) H5 應(yīng)用或 APP 上傳圖片,系統(tǒng)分析后返回分類結(jié)果。為了表演 OAuth2 體系認(rèn)證、授權(quán)、鑒權(quán)和權(quán)限控制的全流程,我們構(gòu)建一個(gè)最簡(jiǎn)化的 IBCS 演示項(xiàng)目:
| 服務(wù)名 | 類別 | 描述 | 技術(shù)選型 |
|---|---|---|---|
| ibc-service | 內(nèi)部服務(wù) | 資源服務(wù)器角色,圖像分類服務(wù) | Spring Boot 開(kāi)發(fā)的 RESTful 服務(wù) |
| idp | 內(nèi)部服務(wù) | 授權(quán)服務(wù)器角色,具體指負(fù)責(zé)認(rèn)證、授權(quán)和鑒權(quán) | Spring Boot 開(kāi)發(fā) |
| demo-h5 | 外部應(yīng)用 | demo 應(yīng)用的前端 | Antd-Pro 開(kāi)發(fā) |
| demo-service | 外部應(yīng)用 | demo 應(yīng)用的后端 | Spring Boot 開(kāi)發(fā) |
這里設(shè)計(jì)了一個(gè) demo 應(yīng)用,對(duì)于 IBCS 來(lái)說(shuō),demo 應(yīng)用就是一個(gè)外部應(yīng)用,是一個(gè)消費(fèi)者,包括前端 demo-h5 和后端 demo-service。但是從整個(gè)演示項(xiàng)目來(lái)說(shuō),ibc-service、idp、demo-h5 和 demo-service 都是自家應(yīng)用,是相互信任的。
值得注意的是,在現(xiàn)實(shí)世界里,應(yīng)用有兩種類型:一是有 Server 端的應(yīng)用;另一種是無(wú) Server 端的應(yīng)用。前者比較好理解,它可能是前后端分離的 MVC 單體應(yīng)用,也有可能是 REST 型的前后端應(yīng)用,這些都是常見(jiàn)的開(kāi)發(fā)結(jié)構(gòu)。除此之外,還有一些沒(méi)有或者不需要后端的應(yīng)用,比如 SinglePage H5,由 Vue 或 React 開(kāi)發(fā)的簡(jiǎn)單 H5,整個(gè)應(yīng)用僅由 JavaScript 代碼組成,前端即應(yīng)用的全部。這種類型的應(yīng)用,有一個(gè)最大的安全問(wèn)題,即 client_secret 如何安全存儲(chǔ),在無(wú) Server 場(chǎng)景中無(wú)論是經(jīng)典的授權(quán)碼模式還是密碼模式,都無(wú)法有效解決這個(gè)問(wèn)題,因?yàn)橐粋€(gè)全部由 JavaScript 寫成的程序,本質(zhì)上是完全透明的,client_secret 存在泄露的風(fēng)險(xiǎn)。client_secret 將導(dǎo)致客戶端被偽造,即攻擊者在拿到 client_id 和 client_secret 后偽造一個(gè) App 發(fā)起虛假請(qǐng)求。當(dāng)然許多平臺(tái)會(huì)在 idp 端增加 redirect_uri 的綁定或 IP/域名白名單機(jī)制,從而有效降低偽造/劫持等安全風(fēng)險(xiǎn)。
有人說(shuō) OAuth 2.0 規(guī)范提出的 PKCE(Proof Key for Code Exchange by OAuth Public Clients)協(xié)議可以解決這個(gè)問(wèn)題,這是一個(gè)錯(cuò)誤的觀點(diǎn),PKCE 在技術(shù)原理上并不能解決 client_secret 泄露的風(fēng)險(xiǎn),所以對(duì)安全性要求高的情況下最好不要采用無(wú) Server 端方式。不過(guò) PKCE 作為一種增強(qiáng)協(xié)議可以搭配 OAuth2 組合使用以提高整體安全性。其主要流程為:由暴露在外的“前端”自身生成一串隨機(jī)的驗(yàn)證碼,再根據(jù)驗(yàn)證碼生成挑戰(zhàn)碼,然后用挑戰(zhàn)碼向授權(quán)服務(wù)器請(qǐng)求授權(quán)碼,授權(quán)服務(wù)器保存挑戰(zhàn)碼后返回授權(quán)碼給“前端”,“前端”拿到授權(quán)碼后用第一步生成的隨機(jī)驗(yàn)證碼+授權(quán)碼向授權(quán)服務(wù)器申請(qǐng)令牌,授權(quán)服務(wù)器拿到驗(yàn)證碼+授權(quán)碼后根據(jù)此驗(yàn)證碼生成挑戰(zhàn)碼,如果生成的挑戰(zhàn)碼跟上一步保存的挑戰(zhàn)碼一致則頒發(fā)令牌。有興趣的同學(xué)可以自行查閱相關(guān)資料。
場(chǎng)景假設(shè) B:相冊(cè)預(yù)覽系統(tǒng)(PAPS,Photo Album Preview System)
PAPS 是一個(gè)社交平臺(tái)的子系統(tǒng),與 IBCS 類似,采用 RESTful API 對(duì)外交互,主要功能是允許用戶預(yù)覽自己的相冊(cè),我們構(gòu)建一個(gè)最簡(jiǎn)化的 PAPS 演示項(xiàng)目:
| 服務(wù)名 | 類別 | 描述 | 技術(shù)選型 |
|---|---|---|---|
| photo-service | 內(nèi)部服務(wù) | 資源服務(wù)器角色,相冊(cè)預(yù)覽服務(wù) | Spring Boot 開(kāi)發(fā)的 RESTful 服務(wù) |
| idp | 內(nèi)部服務(wù) | 授權(quán)服務(wù)器角色,具體指負(fù)責(zé)認(rèn)證、授權(quán)和鑒權(quán) | Spring Boot 開(kāi)發(fā) |
| demo-h5 | 外部應(yīng)用 | demo 應(yīng)用的前端 | Antd-Pro 開(kāi)發(fā) |
| demo-service | 外部應(yīng)用 | demo 應(yīng)用的后端 | Spring Boot 開(kāi)發(fā) |
兩個(gè)演示案例唯一的不同就在于核心服務(wù)不同,一個(gè)是圖像分類服務(wù),一個(gè)是相冊(cè)預(yù)覽服務(wù)??雌饋?lái)似乎沒(méi)什么差別,實(shí)際上他們的業(yè)務(wù)模型還很不同的,接下來(lái)會(huì)分析這一點(diǎn)。
授權(quán)碼模式 vs 密碼模式 vs 客戶端模式
大家思考一下,授權(quán)碼模式、密碼模式和客戶端模式,在不同的業(yè)務(wù)場(chǎng)景下該如何選擇?
無(wú)非有兩種做法:
- 不管三七二十幾,整個(gè)項(xiàng)目只使用一種授權(quán)模式,簡(jiǎn)單明了;
- 仔細(xì)分析各種業(yè)務(wù)場(chǎng)景,給予合適的模式選型。
第一種做法,簡(jiǎn)單粗暴,看起來(lái)好像不可取,但也不能一棍子打死,具體還是看項(xiàng)目的業(yè)務(wù)模型、設(shè)計(jì)目標(biāo)和具體要求。但僅從科學(xué)嚴(yán)謹(jǐn)或者技術(shù)研究角度來(lái)說(shuō),肯定是第二種方式更加可取。接下來(lái),我們就以第二種做法展開(kāi),詳細(xì)分析下模式選型的問(wèn)題。
授權(quán)碼模式和密碼模式
我們先來(lái)看授權(quán)碼模式和密碼模式之間的比較,大家知道,授權(quán)碼模式是 OAuth2 體系安全性最高的模式,密碼模式與其相比,主要差別是少了一層用戶確認(rèn)授權(quán)的動(dòng)作,缺乏這一動(dòng)作就導(dǎo)致在授權(quán)階段,用戶需要把用戶名密碼告知客戶端,造成潛在的密碼泄露風(fēng)險(xiǎn)。我們看一下對(duì)比:
| 比較項(xiàng) | 授權(quán)碼模式 | 密碼模式 |
|---|---|---|
| 適用場(chǎng)景 | 不可信/第三方認(rèn)證和授權(quán)、可信/內(nèi)部服務(wù)認(rèn)證和授權(quán) | 可信/內(nèi)部服務(wù)認(rèn)證和授權(quán) |
| 開(kāi)發(fā)難度 | 較為復(fù)雜 | 相對(duì)簡(jiǎn)單 |
| 安全性 | 最高 | 只要不運(yùn)用于不可信/第三方場(chǎng)景則同樣安全 |
在這里,可信/內(nèi)部服務(wù)場(chǎng)景的定義是相對(duì)的概念,指納入同一套 OAuth2 體系的應(yīng)用和服務(wù),且這些應(yīng)用和服務(wù)是由相同的或者相互信任的團(tuán)隊(duì)開(kāi)發(fā),也可以稱作第一方應(yīng)用。打個(gè)比方,微服務(wù)架構(gòu)下的 B2C 商城系統(tǒng),基本組成有前端 H5、無(wú)線端 APP、API 網(wǎng)關(guān)、認(rèn)證授權(quán)服務(wù)、訂單服務(wù)、商品服務(wù)等,由于上述所有組成部分都同屬于一套 OAuth2 體系,且都由相同團(tuán)隊(duì)開(kāi)發(fā),那么他們?nèi)細(xì)w屬于可信/內(nèi)部服務(wù)場(chǎng)景。
那么為什么又說(shuō)是相對(duì)概念?我們把視角聚焦到整個(gè)商城系統(tǒng),毫無(wú)疑問(wèn),網(wǎng)關(guān)屬于安全邊界,網(wǎng)關(guān)以內(nèi)的認(rèn)證授權(quán)服務(wù)、訂單服務(wù)、商品服務(wù)屬于內(nèi)部服務(wù),而前端 H5、無(wú)線端 APP 則屬于外部應(yīng)用,如果這些外部應(yīng)用是其他團(tuán)隊(duì)開(kāi)發(fā),我們也可以定義它們?yōu)榈谌綉?yīng)用。這樣就以網(wǎng)關(guān)為邊界,劃分出了內(nèi)部服務(wù)和外部服務(wù),這就是所說(shuō)的相對(duì)概念。
那么,電商系統(tǒng)的前端 H5、無(wú)線端 APP 在認(rèn)證和授權(quán)階段,采用授權(quán)碼模式和采用密碼模式,有什么差別嗎?授權(quán)碼模式有一層用戶確認(rèn)授權(quán)的動(dòng)作,從而避免泄露用戶名和密碼給第三方應(yīng)用,除此之外,兩者之間幾乎提供了相同的安全流程。這里所指的第三方應(yīng)用不正是前端 H5 和無(wú)線端 APP 嗎?都是自家開(kāi)發(fā)的應(yīng)用,自然是可信的,因此無(wú)須擔(dān)憂泄露不泄露的風(fēng)險(xiǎn)。
以此來(lái)看,在上述條件的限定下,兩種模式的安全性打平了。遵循“簡(jiǎn)化原則”,采用密碼模式即可,當(dāng)然喜歡挑戰(zhàn)復(fù)雜的同學(xué)選擇授權(quán)碼模式也是可以的。
但是也不是說(shuō)授權(quán)碼模式就可以被密碼模式取代了,授權(quán)碼模式主要的應(yīng)用場(chǎng)景,是在第三方/不可信應(yīng)用的登錄和授權(quán),主要解決在不泄露用戶密碼的情況下如何安全授權(quán)某個(gè)應(yīng)用向另一個(gè)應(yīng)用提供用戶資源的問(wèn)題,舉例來(lái)說(shuō),某第三方應(yīng)用(客戶端)需要獲得用戶(資源所有者)在另一個(gè)不可信應(yīng)用(資源服務(wù)器)上的該用戶的用戶數(shù)據(jù)(資源)的場(chǎng)景就特別適合采用授權(quán)碼模式。
綜上,選擇授權(quán)碼模式還是密碼模式,具體要根據(jù)業(yè)務(wù)場(chǎng)景來(lái)確定,其中的關(guān)鍵決策點(diǎn)是應(yīng)用或服務(wù)之間是否互相信任。
客戶端模式
PAPS 相冊(cè)預(yù)覽系統(tǒng)演示案例應(yīng)該采用何種授權(quán)模式?
回答這個(gè)問(wèn)題之前,大家先思考一個(gè)問(wèn)題:在 PAPS 中,資源所有者所指代的對(duì)象是什么?
首先要明確資源是什么,其次該資源是受保護(hù)的,最后資源歸誰(shuí)所有,誰(shuí)就是資源所有者。在 PAPS 中,很明顯受保護(hù)的資源是用戶的相冊(cè),資源所有者自然是用戶本人。
明確資源所有者的含義后,再根據(jù)前文的分析,毫無(wú)疑問(wèn):如果 PAPS 的 demo 應(yīng)用是第三方的不可信應(yīng)用,則應(yīng)該采用授權(quán)碼模式;如果是第一方可信應(yīng)用,則可以采用密碼模式,當(dāng)然不怕麻煩也可以用授權(quán)碼模式。
接下來(lái),我們來(lái)分析 IBCS 圖片分類系統(tǒng)演示案例該用何種模式。
同樣地,回答這個(gè)問(wèn)題之前,大家再思考一下:在 IBCS 中,資源所有者所指代的對(duì)象是什么?
首先資源所有者所指代的對(duì)象不是一成不變的。在 IBCS 演示案例中,demo 應(yīng)用向 ibc-service 傳送一張圖片,并希望返回分類結(jié)果,那么這里面的受保護(hù)資源具體是什么呢?不似 PAPS 相冊(cè)預(yù)覽服務(wù)有實(shí)體資源概念,其受保護(hù)資源是用戶的相冊(cè),而 IBCS 難以抽象出一個(gè)實(shí)體的資源來(lái)。
可以這么理解,IBCS 提供的核心能力是圖片分類算法,這就是它的受保護(hù)資源,圖片分類算法的所有權(quán)人顯然是持有此算法的實(shí)體組織或個(gè)人,因此資源所有者是該實(shí)體組織或個(gè)人。那么矛盾點(diǎn)來(lái)了,以密碼模式為例,按照 OAuth2 的設(shè)計(jì),資源所有者向客戶端提供用戶名和密碼,客戶端將 client_id 和 client_secret 連同該用戶名和密碼,向授權(quán)服務(wù)器申請(qǐng)令牌,此處的資源所有者是 IBCS 的普通用戶,但是按照剛才所述,資源所有者應(yīng)該是持有圖片分類算法的實(shí)體組織或個(gè)人,這不就矛盾了嗎?
因此,從資源所有者的角度來(lái)分析,IBCS 演示案例不適合采用授權(quán)碼模式,也不適合采用密碼模式,客戶端模式才是最佳選擇。IBCS 并沒(méi)有用戶的資源,授權(quán)碼模式和密碼模式都是需要用戶授權(quán)才能跑通的,而 IBCS 提供的資源或服務(wù)并不屬于用戶,所以法理上來(lái)說(shuō)不需要用戶的授權(quán),IBCS 提供的分析服務(wù)是跟用戶無(wú)關(guān)的。
以上就是選擇何種授權(quán)模式的一種分析模型,從資源所有者的維度切入??偨Y(jié)一下:
| 案例 | 場(chǎng)景 | 適合模式 |
|---|---|---|
| IBCS 圖像分類系統(tǒng)案例 | 可信/內(nèi)部服務(wù) | 客戶端模式 |
| IBCS 圖像分類系統(tǒng)案例 | 不可信/外部應(yīng)用 | 客戶端模式 |
| PAPS 相冊(cè)預(yù)覽系統(tǒng)案例 | 可信/內(nèi)部服務(wù) | 密碼模式 |
| PAPS 相冊(cè)預(yù)覽系統(tǒng)案例 | 不可信/外部應(yīng)用 | 授權(quán)碼模式 |
其實(shí)資源所有者的具體指代對(duì)于實(shí)際開(kāi)發(fā)來(lái)說(shuō),并不是一個(gè)重要的方面,關(guān)鍵是開(kāi)發(fā)人員要有自己的理解,能夠靈活巧用。說(shuō)句題外話,雖然 OAuth2 是當(dāng)今領(lǐng)域內(nèi)的權(quán)威,但我們也不應(yīng)盲目地?zé)o條件相信權(quán)威,技術(shù)發(fā)展一定是有漏洞和不完美之處的。
密碼模式的典型架構(gòu)層次和主要流程
我們以 PAPS 相冊(cè)預(yù)覽系統(tǒng)為例,介紹密碼模式的架構(gòu)層次和主要流程。

如圖所示,是密碼模式的最精簡(jiǎn)架構(gòu)層次,在實(shí)際開(kāi)發(fā)中可以此作為基礎(chǔ)進(jìn)行擴(kuò)展。密碼模式涉及到五種主要角色,另外還有一個(gè)用戶代理/瀏覽器角色:
- 用戶代理/瀏覽器:一般單體應(yīng)用都是前后端分離的 MVC 結(jié)構(gòu),從這個(gè)角度看,這里具體可以將用戶代理/瀏覽器理解為前端的 H5 應(yīng)用或者無(wú)線端的 APP,換句話說(shuō) H5/APP 承載用戶的交互操作而成為用戶代理。具體到 PAPS 演示案例就是 demo-h5;
- 客戶端:具體指單體應(yīng)用的后端服務(wù),具體到 PAPS 演示案例就是 demo-service;
- 授權(quán)服務(wù)器:具體指負(fù)責(zé)認(rèn)證、授權(quán)和鑒權(quán)的 IDP(Identify Provider),也是 OAuth2 體系的核心服務(wù),也可以簡(jiǎn)單叫做 auth-service。具體到 PAPS 演示案例就是 idp;
- 受保護(hù)資源:即資源服務(wù)器,一般是內(nèi)部服務(wù),比如產(chǎn)品服務(wù)、訂單服務(wù)等。具體到 PAPS 演示案例就是 photo-service;
- 資源所有者:顧名思義,即受保護(hù)資源的所有者,一般具體指代用戶自己。具體到 PAPS 演示案例就是用戶。
整個(gè)流程分為兩個(gè)階段:
- 第一階段:認(rèn)證授權(quán)階段
- 用戶代理(demo-h5)將用戶輸入的用戶名和密碼,發(fā)送給客戶端(demo-service);
- 客戶端(demo-service)將用戶輸入的用戶名和密碼,連同 client_id + client_secret (由 idp 分配)一起發(fā)送到 idp 以請(qǐng)求令牌,如果 idp 約定了 scope 則還需要帶上 scope 參數(shù);
- idp 首先驗(yàn)證 client_id + client_secret 的合法性,再檢查 scope 是否無(wú)誤,最后驗(yàn)證用戶名和密碼是否正確,正確則生成 token。這一步也叫“認(rèn)證”;
- idp 返回認(rèn)證結(jié)果給客戶端,認(rèn)證通過(guò)返回 token,認(rèn)證失敗返回 401。如果認(rèn)證成功則此步驟也叫“授權(quán)”;
- 客戶端收到 token 后進(jìn)行暫存,并創(chuàng)建對(duì)應(yīng)的 session;
- 客戶端頒發(fā) cookie 給用戶代理/瀏覽器。
至此,認(rèn)證授權(quán)階段完成。其中步驟 5-6 也有其他會(huì)話方案,比如 REST 型應(yīng)用可能會(huì)將 token 存儲(chǔ)在瀏覽器端,但 session/cookie 方案無(wú)疑是最穩(wěn)妥的選擇。
在一般的 Web 應(yīng)用中,可以將此階段看作是一次用戶登錄過(guò)程。
- 第二階段:授權(quán)后請(qǐng)求資源階段
- 用戶通過(guò)用戶代理(demo-h5)訪問(wèn)“我的相冊(cè)”頁(yè)面,用戶代理攜帶 cookie 向客戶端(demo—service)發(fā)起請(qǐng)求;
- 客戶端通過(guò) session 找到對(duì)應(yīng)的 token,攜帶此 token 向資源服務(wù)器(photo-service)發(fā)起請(qǐng)求;
- 資源服務(wù)器(photo-service)向 idp 請(qǐng)求驗(yàn)證 token 有效性;
- idp 校驗(yàn) token 有效性,再根據(jù) scope 判斷客戶端(demo-service)是否有權(quán)限調(diào)用此 API,最后返回校驗(yàn)結(jié)果給資源服務(wù)器。這一步也叫“鑒權(quán)”;
- 資源服務(wù)器根據(jù) idp 檢驗(yàn)結(jié)果(true/false 或其他等效手段)決定是否返回用戶相冊(cè)數(shù)據(jù)給客戶端。如果 token 校驗(yàn)失敗則返回 401 給客戶端,如果 scope 檢查不通過(guò)則返回 403。這一步也叫“權(quán)限控制”。
至此,授權(quán)后請(qǐng)求資源階段完成。
事實(shí)上 scope 參數(shù)不是核心的內(nèi)容,實(shí)際工作中為了簡(jiǎn)化開(kāi)發(fā)步驟甚至可以忽略它。scope 參數(shù)是用來(lái)約束客戶端的權(quán)限的,跟用戶權(quán)限(authorities)是不同的。比如可以在 idp 中利用 scope 參數(shù)約束某客戶端只能發(fā)起讀(GET)型請(qǐng)求,或只能調(diào)用指定的幾個(gè) API 等,具體業(yè)務(wù)邏輯自行編寫。
密碼模式的微服務(wù)架構(gòu)層次和主要流程
我們?nèi)砸?PAPS 相冊(cè)預(yù)覽系統(tǒng)為例,介紹密碼模式在微服務(wù)場(chǎng)景下的架構(gòu)層次和主要流程。

微服務(wù)場(chǎng)景下,增加了一個(gè)網(wǎng)關(guān),網(wǎng)關(guān)實(shí)際上是一個(gè)反向代理,將用戶的請(qǐng)求轉(zhuǎn)發(fā)到內(nèi)部服務(wù)器。類似地,微服務(wù)場(chǎng)景下也分為兩個(gè)階段,而且第一階段沒(méi)什么變化,主要不同在于第二階段:
- 用戶通過(guò)用戶代理(demo-h5)訪問(wèn)“我的相冊(cè)”頁(yè)面,用戶代理攜帶 cookie 向客戶端(demo—service)發(fā)起請(qǐng)求;
- 客戶端通過(guò) session 找到對(duì)應(yīng)的 token,攜帶此 token 向網(wǎng)關(guān)發(fā)起對(duì)資源服務(wù)器(photo-service)的請(qǐng)求;
- 網(wǎng)關(guān)截取 token 連同本次請(qǐng)求的細(xì)節(jié),一并向 idp 請(qǐng)求校驗(yàn);
- idp 校驗(yàn) token 有效性,再根據(jù) scope 和請(qǐng)求細(xì)節(jié)判斷客戶端(demo-service)是否有權(quán)限調(diào)用此 API,最后返回校驗(yàn)結(jié)果給網(wǎng)關(guān)。如果校驗(yàn)全部通過(guò),idp 生成 JWT 并返回給網(wǎng)關(guān);如果 token 校驗(yàn)失敗返回 401;如果 scope 檢查不通過(guò)則返回 403;
- 如果校驗(yàn)通過(guò),網(wǎng)關(guān)將得到 JWT,攜帶此 JWT 轉(zhuǎn)發(fā)請(qǐng)求到資源服務(wù)器;
- 資源服務(wù)器解析 JWT 得到用戶信息,查詢用戶相冊(cè)數(shù)據(jù)后返回給網(wǎng)關(guān);
- 網(wǎng)關(guān)將用戶相冊(cè)數(shù)據(jù)返回給客戶端。
此流程有兩項(xiàng)重大變化:一是加入網(wǎng)關(guān)使得整個(gè)流程復(fù)雜了一些;二是網(wǎng)關(guān)以內(nèi)使用 JWT 作為令牌。關(guān)于這兩點(diǎn)的解讀,本文不再贅述,感興趣的同學(xué)可以參閱本人早前的文章《微服務(wù)架構(gòu)下的統(tǒng)一身份認(rèn)證和授權(quán)》。
值得注意的是,步驟 9-11,還有另一種處理方法,即將 scope 客戶端權(quán)限檢查放到網(wǎng)關(guān)進(jìn)行:
- 網(wǎng)關(guān)截取 token 后向 idp 請(qǐng)求校驗(yàn);
- idp 校驗(yàn) token 有效性,通過(guò)校驗(yàn)則根據(jù) token 查詢用戶信息和 scope,生成 JWT 返回給網(wǎng)關(guān);如果不通過(guò)則返回 401;
- 網(wǎng)關(guān)得到 JWT,解析后根據(jù) scope 判斷客戶端是否有權(quán)限調(diào)用此 API,如有則攜帶 JWT 轉(zhuǎn)發(fā)請(qǐng)求到資源服務(wù)器,否則返回 403 給客戶端。
客戶端權(quán)限檢查放到網(wǎng)關(guān),則網(wǎng)關(guān)要維護(hù) scope 和客戶端權(quán)限的邏輯。
客戶端模式的微服務(wù)架構(gòu)層次和主要流程
我們以 IBCS 圖片分類系統(tǒng)為例,介紹客戶端模式在微服務(wù)場(chǎng)景下的架構(gòu)層次和主要流程。

可以看到,客戶端模式流程比較簡(jiǎn)單,這里就不再敘述具體流程了,不過(guò)請(qǐng)注意第 2 步:
- 客戶端用向 idp 申請(qǐng)令牌之前,應(yīng)該先檢查是否緩存了有效令牌,有的話直接跳到第 6 步發(fā)起服務(wù)訪問(wèn)請(qǐng)求。
授權(quán)碼模式的微服務(wù)架構(gòu)層次和主要流程
我們?nèi)砸?PAPS 相冊(cè)預(yù)覽系統(tǒng)為例,介紹授權(quán)碼模式在微服務(wù)場(chǎng)景下的架構(gòu)層次和主要流程。

整個(gè)流程分為兩個(gè)階段:
- 第一階段:認(rèn)證授權(quán)階段
- 用戶在用戶代理(demo-h5)處點(diǎn)擊登錄按鈕,或請(qǐng)求授權(quán)登錄按鈕,此操作將訪問(wèn)客戶端的某個(gè) URI;
- 客戶端(demo-service)將用戶導(dǎo)向 idp 提供的認(rèn)證授權(quán)頁(yè)面,并在頁(yè)面 ULR 參數(shù)中攜帶 client_id,response_type=code,redirect_uri(可選),scope(可選),state(可選);
- 用戶通過(guò)用戶代理(demo-h5),在 idp 的認(rèn)證授權(quán)頁(yè)面選擇是否給予授權(quán),如用戶未登錄,則需要先登錄后再操作;
- 用戶給予授權(quán),idp 將用戶導(dǎo)向 redirect_uri 指定的頁(yè)面,并附加授權(quán)碼(code);如果未指定 redirect_uri,則導(dǎo)向發(fā)起該請(qǐng)求時(shí)的 URI,同時(shí)附加授權(quán)碼(code);
- 客戶端收到授權(quán)碼(code),向 idp 發(fā)起令牌申請(qǐng),同時(shí)附上 client_id(必填) + client_secret(必填) + state(如有) + scope(如有)。注意這一步是客戶端在后臺(tái)發(fā)起的,用戶層面無(wú)法感知;
- idp 收到請(qǐng)求后,先核對(duì) client_id + client_secret + scope(如有)是否無(wú)誤,然后校驗(yàn)授權(quán)碼(code),全部正確后頒發(fā) token 給客戶端。
- 第二階段:授權(quán)后請(qǐng)求資源階段
該階段的流程,與密碼模式的微服務(wù)場(chǎng)景流程一致,此處不在贅述。
對(duì)于 REST 型 demo 應(yīng)用
如果 demo 應(yīng)用是一個(gè) REST 型應(yīng)用,則在第 1、2 步驟中,還可以這么處理:
- 用戶在用戶代理(demo-h5)處點(diǎn)擊登錄按鈕或請(qǐng)求授權(quán)登錄按鈕后,通知客戶端(demo-service),客戶端收到通知后返回重定向的指示,以及 scope(可選),state(可選)等;
- demo-h5 收到響應(yīng)后,直接將用戶導(dǎo)向 idp 提供的認(rèn)證授權(quán)頁(yè)面,并在頁(yè)面 URL 參數(shù)中攜帶客戶端返回的參數(shù)(除了 state 參數(shù)外,其他參數(shù)可以在 demo-h5 中寫死)client_id,response_type=code,redirect_uri(必選),scope(可選),state(可選),其中 redirect_uri 建議是必選的,而且必須是客戶端提供的 URI(回調(diào)地址)。
還可以這么處理:
- 用戶在用戶代理(demo-h5)處點(diǎn)擊登錄按鈕或請(qǐng)求授權(quán)登錄按鈕后,直接將用戶導(dǎo)向 idp 提供的認(rèn)證授權(quán)頁(yè)面,并在頁(yè)面 URL 參數(shù)中攜帶 client_id,response_type=code,redirect_uri(必選),scope(可選),但是不需要攜帶 state 參數(shù),因?yàn)榭蛻舳瞬恢来藚?shù)的存在,其中 redirect_uri 建議是必選的,而且必須是客戶端提供的 URI(回調(diào)地址)。
至此,授權(quán)碼模式的認(rèn)證授權(quán)全流程完畢。
討論:客戶端第一次將用戶導(dǎo)向 idp 提供的認(rèn)證授權(quán)頁(yè)面時(shí),idp 是否需要驗(yàn)證客戶端的身份呢?或者說(shuō)需不需要提供 client_secret 呢?
微服務(wù)網(wǎng)關(guān)的負(fù)載問(wèn)題
在微服務(wù)場(chǎng)景下,大量的請(qǐng)求和響應(yīng)都經(jīng)過(guò)網(wǎng)關(guān)轉(zhuǎn)發(fā),我們?cè)O(shè)想一下,如果類似 PAPS 相冊(cè)系統(tǒng)那樣,返回的是圖片數(shù)據(jù),而且不采用 CDN 分發(fā)網(wǎng)絡(luò)或文件存儲(chǔ)服務(wù)等技術(shù),那么圖片流通過(guò)網(wǎng)關(guān)再返回給用戶,網(wǎng)關(guān)的負(fù)載是不是將會(huì)非常龐大呢?
當(dāng)然,網(wǎng)關(guān)本身可以做負(fù)載均衡,可以引入緩存,數(shù)據(jù)流可以做 CDN 處理等,這些都是非常好的高性能方案,除此之外,有沒(méi)有其他辦法呢?
這里引入一個(gè)網(wǎng)絡(luò)技術(shù)領(lǐng)域的概念——負(fù)載均衡有三種模式:反向代理、透?jìng)髂J胶腿悄J?。這里簡(jiǎn)單介紹一下三角模式:
假設(shè)一種網(wǎng)絡(luò)結(jié)構(gòu),包括 Web 服務(wù)器、PC 客戶端和負(fù)載均衡器。PC 通過(guò)負(fù)載均衡器發(fā)起對(duì) Web 服務(wù)器的訪問(wèn)。在三角模式下的訪問(wèn)流程如下:
- PC 向負(fù)載均衡器發(fā)起對(duì) Web 服務(wù)器的訪問(wèn)請(qǐng)求;
- 負(fù)載均衡器將 PC 的請(qǐng)求轉(zhuǎn)發(fā)給 Web 服務(wù)器;
- Web 服務(wù)器收到請(qǐng)求后,直接將響應(yīng)數(shù)據(jù)發(fā)送給 PC 端。
PC、Web 服務(wù)器和負(fù)載均衡器三者呈三角形狀,因此叫做三角模式。這個(gè)模式的優(yōu)點(diǎn)就是負(fù)載均衡器只負(fù)責(zé)轉(zhuǎn)發(fā)請(qǐng)求,而響應(yīng)數(shù)據(jù)包則不需要接收和轉(zhuǎn)發(fā),從而大副降低負(fù)載均衡器自身的數(shù)據(jù)流通壓力。三角模式也類似于 LVS 的 DR 模式,LVS 調(diào)度器只負(fù)責(zé)接收和轉(zhuǎn)發(fā)請(qǐng)求,后端的集群服務(wù)器返回的真實(shí)數(shù)據(jù)包將直接發(fā)往請(qǐng)求的客戶端。
按照這個(gè)思路,我們可以將三角模式引入網(wǎng)關(guān),從而大副降低網(wǎng)關(guān)的負(fù)載壓力。
令牌的復(fù)用問(wèn)題
我們?cè)O(shè)想一個(gè)場(chǎng)景,團(tuán)隊(duì)研發(fā)的平臺(tái)同時(shí)包含了 IBCS 圖片分類服務(wù)和 PAPS 相冊(cè)預(yù)覽服務(wù),那么用戶在登錄平臺(tái)(用密碼模式認(rèn)證授權(quán))后,先訪問(wèn)“我的相冊(cè)”,然后從中選擇一張照片發(fā)起物品識(shí)別的請(qǐng)求。
根據(jù)文章的模式選型分析,IBCS 服務(wù)應(yīng)采用客戶端模式,PAPS 服務(wù)應(yīng)采用密碼模式,那么,客戶端是否應(yīng)該申請(qǐng)兩套令牌?
回答這個(gè)問(wèn)題,我們還是要從具體場(chǎng)景切入分析:
- 如果用戶需要登錄平臺(tái)后才能使用 IBCS 和 PAPS 的服務(wù),那么,只需要用密碼模式一種令牌即可;
- 如果 PAPS 功能無(wú)需登錄,游客也能使用,那么密碼模式和客戶端模式要分開(kāi)處理。
授權(quán)碼模式是最嚴(yán)格的,密碼模式次之,客戶端模式最差,因此一般情況下,授權(quán)碼模式的令牌可以給其他模式使用,密碼模式令牌可以給客戶端模式使用,客戶端模式只能自己使用。
三、Spring 家族 OAuth2 相關(guān)組件概覽
好了,從本節(jié)開(kāi)始我們脫離枯燥的理論環(huán)節(jié),進(jìn)入一樣枯燥的實(shí)戰(zhàn)開(kāi)發(fā)頻道。
目前構(gòu)建 OAuth2 授權(quán)系統(tǒng)有三種方式:一是基于主流的開(kāi)源組件構(gòu)建;二是接入第三方授權(quán)服務(wù)(如 Google、GitHub OAuth2);三是根據(jù) OAuth2 的標(biāo)準(zhǔn)規(guī)范自行開(kāi)發(fā)相關(guān)授權(quán)組件。
常用的開(kāi)源組件有 RedHat Keycloak、Spring Security、Spring Security OAuth2,以及剛起步的 Spring Authorization Server 等。值得一提的是 RedHat Keycloak,它是一款開(kāi)源、成熟的 IAM 解決方案,功能強(qiáng)大且可私有化部署。
在 Spring 的開(kāi)發(fā)生態(tài)里,建議采用 Spring Security 方向的開(kāi)源組件方案,可擴(kuò)展性好,定制性強(qiáng),用戶廣泛且社區(qū)活躍。
Spring Security OAuth2 目前已停止更新,官方不推薦繼續(xù)使用。相關(guān)功能已經(jīng)遷移到 Spring Security,但授權(quán)服務(wù)器(Authorization Server)功能并未包含。Authorization Server 功能將由 Spring Security 團(tuán)隊(duì)主導(dǎo)開(kāi)發(fā)的 Spring Authorization Server 開(kāi)源組件提供,詳細(xì)信息請(qǐng)查看官方通告:https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server
Spring Authorization Server 項(xiàng)目目前還在迭代中,該項(xiàng)目的開(kāi)發(fā)計(jì)劃托管在 ZenHub 上,感興趣的同學(xué)可以自行了解: https://app.zenhub.com/workspaces/authorization-server-5e8f3182b5e8f5841bfc4902/board?repos=248032165
一)基于 Spring Framework
- spring-security
核心組件,當(dāng)前幾乎所有 Spring OAuth2 開(kāi)源技術(shù)方案都依賴于它。核心模塊: spring-security-core。
<dependencies>
<!-- ... other dependency elements ... -->
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-web</artifactId>
<version>5.5.1</version>
</dependency>
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-config</artifactId>
<version>5.5.1</version>
</dependency>
</dependencies>
- [已停止更新] spring-security-oauth2
依賴于 spring-security。該組件現(xiàn)已合并到 spring-security 中,官方已不建議使用。在此之前是 Spring Security 團(tuán)隊(duì)官方維護(hù)的唯一且被廣泛使用的組件。
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.5.1.RELEASE</version>
</dependency>
- spring-security-oauth2-authorization-server
依賴于 spring-security。在 Spring Security 官方宣布停止 spring-security-oauth2 組件更新后,推出的授權(quán)服務(wù)器 Authorization Server 的替代組件。 spring-security 整合 spring-security-oauth2 后,并不包括 Authorization Server 功能,因此如果需要開(kāi)發(fā)此功能,則要搭配 spring-security-oauth2-authorization-server 一起使用。
<!-- https://mvnrepository.com/artifact/org.springframework.security.experimental/spring-security-oauth2-authorization-server -->
<dependency>
<groupId>org.springframework.security.experimental</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.1.2</version>
</dependency>
二)基于 Spring Boot
- spring-boot-starter-security
依賴于 spring-security。相當(dāng)于 spring-boot + spring-security ,默認(rèn)包含 SecurityAutoConfiguration.class ,因此會(huì)執(zhí)行一些自動(dòng)化配置,可以簡(jiǎn)化開(kāi)發(fā)步驟。如果想關(guān)閉自動(dòng)配置,可以修改 Spring Boot 啟動(dòng)注解為 @SpringBootApplication(exclude = { SecurityAutoConfiguration.class })
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
三)基于 Spring Cloud
- spring-cloud-starter-oauth2
依賴于 spring-boot-starter-security + spring-security-oauth2,并額外提供了許多功能實(shí)現(xiàn),在微服務(wù)場(chǎng)景下比較實(shí)用。
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>Hoxton.SR12</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>
三、核心組件選型
最佳方案: spring-boot-starter-security + spring-authorization-server
由于 spring-security-oauth2 已經(jīng)遷移到 spring-security,而 spring-boot-starter-security 集成了 spring-security,且做了許多簡(jiǎn)化配置,特別適合于構(gòu)建 spring-boot 程序。截至 2021年8月,spring-authorization-server 的最新版本是 0.1.2,最新的消息請(qǐng)關(guān)注官方動(dòng)態(tài) https://spring.io/blog/2020/04/15/announcing-the-spring-authorization-server
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.experimental/spring-security-oauth2-authorization-server -->
<dependency>
<groupId>org.springframework.security.experimental</groupId>
<artifactId>spring-security-oauth2-authorization-server</artifactId>
<version>0.1.2</version>
</dependency>
Spring Authorization Server 項(xiàng)目遵守 OAuth2.1 規(guī)范而不支持 password 模式,因此需要是用此模式的項(xiàng)目可以考慮備選方案。

備選方案: spring-security-oauth2 或 spring-cloud-starter-oauth2
此方案采用了 spring-security-oauth2,已被 Sprnig Security 官方停止更新,因此不再推薦。另外請(qǐng)注意的 spring-security-oauth2 2.4.0.RELEASE 及之后的版本會(huì)提示 deprecated。
- 如果采用 spring-security-oauth2,則 pom 引用如下:
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth/spring-security-oauth2 -->
<dependency>
<groupId>org.springframework.security.oauth</groupId>
<artifactId>spring-security-oauth2</artifactId>
<version>2.3.8.RELEASE</version>
</dependency>
<!-- https://mvnrepository.com/artifact/org.springframework.security.oauth.boot/spring-security-oauth2-autoconfigure -->
<dependency>
<groupId>org.springframework.security.oauth.boot</groupId>
<artifactId>spring-security-oauth2-autoconfigure</artifactId>
<version>2.1.2.RELEASE</version>
</dependency>
<!-- spring-security-oauth2-autoconfigure 2.1.3.RELEASE 及之后的版本會(huì)提示 deprecated。
- 或采用 spring-cloud-starter-oauth2,則 pom 引用如下:
spring-cloud-starter-oauth2 集成了 spring-security-oauth2,且做了許多簡(jiǎn)化配置,是在很長(zhǎng)一段時(shí)間里基于開(kāi)源軟件構(gòu)建 OAuth2 的主要方案之一。
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-oauth2</artifactId>
</dependency>