Part 1 - 理論相關(guān)
作者 freewolf
關(guān)鍵詞
微服務(wù)
、Spring Cloud
瞒大、OAuth 2.0
系洛、JWT
俊性、Spring Security
、SSO
描扯、UAA
寫在前面
作為從業(yè)了十多年的IT行業(yè)和程序的老司機(jī)定页,今天如果你說你不懂微服務(wù),都不好意思說自己的做軟件的绽诚。SOA喊了多年典徊,無人不知,但又有多少系統(tǒng)開發(fā)真正的SOA了呢恩够?但是好像一夜之間所有人都投入了微服務(wù)的懷抱卒落。
作為目前最主流的“微服務(wù)框架”,Spring Cloud發(fā)展速度很快蜂桶,成為了最全面的微服務(wù)解決方案儡毕。不管什么軟件體系,什么框架扑媚,安全永遠(yuǎn)是不可能繞開的話題妥曲,我也把它作為我最近一段時間研究微服務(wù)的開篇。
老話題钦购!“如何才能在微服務(wù)體系中保證安全檐盟?”,為了達(dá)成目標(biāo)押桃,這里采用一個簡單而可行方式來保護(hù)Spring Cloud
中服務(wù)的安全葵萎,也就是建立統(tǒng)一的用戶授權(quán)中心。
這里補(bǔ)充說一下什么是Authentication(認(rèn)證)
和Authorization(鑒權(quán))
,其實很簡單羡忘,認(rèn)證關(guān)心你是誰谎痢,鑒權(quán)關(guān)心你能干什么。舉個大家一致都再說的例子卷雕,如果你去機(jī)場乘機(jī)节猿,你持有的護(hù)照代表你的身份,這是認(rèn)證漫雕,你的機(jī)票就是你的權(quán)限滨嘱,你能干什么。
學(xué)習(xí)微服務(wù)并不是一個簡單的探索過程浸间,這不得學(xué)習(xí)很多新的知識太雨,其實不管是按照DDD(Domain Driven Design)領(lǐng)域驅(qū)動設(shè)計中領(lǐng)域模型的方式,還是將微服務(wù)拆分成更小的粒度魁蒜。都會遇到很多新的問題和以前一直都沒解決很好的問題囊扳。隨著不斷的思考,隨著熟悉Facebook/GitHub/AWS
這些機(jī)構(gòu)是如何保護(hù)內(nèi)部資源兜看,答案也逐漸浮出水面锥咸。
為了高效的實現(xiàn)這個目標(biāo),這里采用OAuth 2
和JWT(JSON Web Tokens)
技術(shù)作為解決方案细移,
為什么使用OAuth 2
盡管微服務(wù)在現(xiàn)代軟件開發(fā)中還算一個新鮮事物搏予,但是OAuth 2
已經(jīng)是一個廣泛使用的授權(quán)技術(shù),它讓W(xué)eb開發(fā)者在自己提供服務(wù)中葫哗,用一種安全的方式直訪問Google/Facebook/GitHub
平臺用戶信息。但在我開始闡述細(xì)節(jié)之前球涛,我將揭開聚焦到本文真正的主題:云安全
那么在云服務(wù)中對用戶訪問資源的控制劣针,我們一般都怎么做呢?然我舉一些大家似乎都用過的但又不是很完美的例子亿扁。
我們可以設(shè)置邊界服務(wù)器或者帶認(rèn)證功能的反向代理服務(wù)器捺典,假設(shè)所有訪問請求都發(fā)給它。通過認(rèn)證后从祝,轉(zhuǎn)發(fā)給內(nèi)部相應(yīng)的服務(wù)器襟己。一般在Spring MVC Security
開發(fā)中幾乎都會這樣做的。但這并不安全牍陌,最重要的是擎浴,一旦是有人從內(nèi)部攻擊,你的數(shù)據(jù)毫無安全性毒涧。
其他方式:我們?yōu)樗蟹?wù)建立統(tǒng)一的權(quán)限數(shù)據(jù)庫贮预,并在每次請求前對用戶進(jìn)行鑒權(quán),聽起來某些方面的確有點(diǎn)愚蠢,但實際上這確實是一個可行的安全方案仿吞。
更好的方式: 用戶通過授權(quán)服務(wù)來實現(xiàn)鑒權(quán)滑频,把用戶訪問Session映射成一個Token
。所有遠(yuǎn)程訪問資源服務(wù)器相關(guān)的API必須提供Token
唤冈。然后資源服務(wù)器訪問授權(quán)服務(wù)來識別Token
峡迷,得知Token
屬于哪個用戶,并了解通過這個Token可以訪問什么資源你虹。
這聽起來是個不錯的方案绘搞,對不?但是如何保證Token
的安全傳輸售葡?如何區(qū)分是用戶訪問還是其他服務(wù)訪問看杭?這肯定是我們關(guān)心的問題。
所以上述種種問題讓我們選擇使用OAuth 2
挟伙,其實訪問Facebook/Google
的敏感數(shù)據(jù)和訪問我們自己后端受保護(hù)數(shù)據(jù)沒什么區(qū)別楼雹,并且他們已經(jīng)使用這樣的解決方案很多年,我們只要遵循這些方法就好了尖阔。
OAuth 2
是如何工作的
如果你了解OAuth 2
相關(guān)的原理贮缅,那么在部署OAuth 2
是非常容易的。
讓我們描述下這樣一個場景介却,“某App
希望獲得Tom
在Facebook
上相關(guān)的數(shù)據(jù)”
OAuth 2 在整個流程中有四種角色:
- 資源擁有者(Resource Owner) - 這里是Tom
- 資源服務(wù)器(Resource Server) - 這里是Facebook
- 授權(quán)服務(wù)器(Authorization Server) - 這里當(dāng)然還是Facebook谴供,因為Facebook有相關(guān)數(shù)據(jù)
- 客戶端(Client) - 這里是某App
當(dāng)Tom
試圖登錄Facebook
,某App
將他重定向到Facebook
的授權(quán)服務(wù)器齿坷,當(dāng)Tom
登錄成功桂肌,并且許可自己的Email和個人信息被某App
獲取。這兩個資源被定義成一個Scope(權(quán)限范圍)
永淌,一旦準(zhǔn)許崎场,某App
的開發(fā)者就可以申請訪問權(quán)限范圍中定義的這兩個資源。
+--------+ +---------------+
| |--(A)- Authorization Request ->| Resource |
| | | Owner |
| |<-(B)-- Authorization Grant ---| |
| | +---------------+
| |
| | +---------------+
| |--(C)-- Authorization Grant -->| Authorization |
| Client | | Server |
| |<-(D)----- Access Token -------| |
| | +---------------+
| |
| | +---------------+
| |--(E)----- Access Token ------>| Resource |
| | | Server |
| |<-(F)--- Protected Resource ---| |
+--------+ +---------------+
Tom
允許了權(quán)限請求遂蛀,再次通過重定向返回某App
谭跨,重定向返回時攜帶了一個Access Token(訪問令牌)
,接下來某App
就可以通過這個Access Token
從Facebook
直接獲取相關(guān)的授權(quán)資源(也就是Email和個人信息)李滴,而無需重新做Tom
相關(guān)的鑒權(quán)螃宙。而且每當(dāng)Tom
登錄了某App
,都可以通過之前獲得的Access Token
所坯,直接獲取相關(guān)授權(quán)資源谆扎。
到目前為止,我們?nèi)绾沃苯訉⒁陨蟽?nèi)容用于實際的例子中芹助?OAuth 2
十分友好燕酷,并容易部署籍凝,所有交互都是關(guān)于客戶端和權(quán)限范圍的。
-
OAuth 2
中的客戶端
和權(quán)限范圍
和我們平時的用戶和權(quán)限是否相同苗缩? - 我需要將授權(quán)映射到權(quán)限范圍中或?qū)⒂脩粲成涞娇蛻舳酥校?/li>
- 為什么我需要客戶端饵蒂?
你也許在之前在類似的企業(yè)級開發(fā)案例中嘗試映射過相關(guān)的角色。這會很棘手酱讶!
任何類型的應(yīng)用都提供用戶登錄退盯,登錄結(jié)果是一個Access Token
,所有的之后的API調(diào)用都將這個Access Token
加入HTTP請求頭中泻肯,被調(diào)用服務(wù)去授權(quán)服務(wù)器驗證Access Token
并獲取該Token可訪問的權(quán)限信息渊迁。這樣一來,所有服務(wù)的訪問都會請求另外的服務(wù)來完成鑒權(quán)灶挟。
權(quán)限范圍和角色琉朽,客戶端和用戶
在OAuth 2
中,你可以定義哪個應(yīng)用(網(wǎng)站稚铣、移動客戶端箱叁、桌面應(yīng)用、其他)可以訪問那些資源惕医。這里只有一個尺寸耕漱,來自哪里的哪個用戶可以訪問那些數(shù)據(jù),當(dāng)然也是哪個應(yīng)用或者服務(wù)可以訪問那些資源抬伺。換一種說法螟够,權(quán)限范圍就是控制那些端點(diǎn)對客戶端可見,或者用戶根據(jù)他的權(quán)限來獲取相關(guān)的數(shù)據(jù)峡钓。
在一個在線商店中妓笙,前端可以看做一個客戶端,可以訪問商品能岩、訂單和客戶信息寞宫,但后端可以關(guān)于物流和合同等,另一方面捧灰,用戶可以訪問一個服務(wù)但并不是全部的數(shù)據(jù)淆九,這可以是因為用戶正在使用Web應(yīng)用统锤,當(dāng)他不能的時候毛俏,其他用戶卻可以。服務(wù)之間的訪問時我們要討論的另一個維度饲窿。如果你熟悉數(shù)學(xué)煌寇,我可以說在OAuth 2
中,客戶端-權(quán)限范圍關(guān)系是線性獨(dú)立于用戶-權(quán)限關(guān)系逾雄。
為什么是JWT
OAuth 2
并不關(guān)心去哪找Access Token
和把它存在什么地方的阀溶,生成隨機(jī)字符串并保存Token
相關(guān)的數(shù)據(jù)到這些字符串中保存好腻脏。通過一個令牌端點(diǎn),其他服務(wù)可能會關(guān)心這個Token
是否有效银锻,它可以通過哪些權(quán)限永品。這就是用戶信息URL方法,授權(quán)服務(wù)器為了獲取用戶信息轉(zhuǎn)換為資源服務(wù)器击纬。
當(dāng)我們談及微服務(wù)時鼎姐,我們需要找一個Token
存儲的方式,來保證授權(quán)服務(wù)器可以被水平擴(kuò)展更振,盡管這是一個很復(fù)雜的任務(wù)炕桨。所有訪問微服務(wù)資源的請求都在Http Header中攜帶Token
,被訪問的服務(wù)接下來再去請求授權(quán)服務(wù)器驗證Token
的有效性肯腕,目前這種方式献宫,我們需要兩次或者更多次的請求,但這是為了安全性也沒什么其他辦法实撒。但擴(kuò)展Token
存儲會很大影響我們系統(tǒng)的可擴(kuò)展性姊途,這是我們引入JWT(讀jot)
的原因。
+-----------+ +-------------+
| | 1-Request Authorization | |
| |------------------------------------>| |
| | grant_type&username&password | |--+
| | |Authorization| | 2-Gen
| Client | |Service | | JWT
| | 3-Response Authorization | |<-+
| |<------------------------------------| Private Key |
| | access_token / refresh_token | |
| | token_type / expire_in / jti | |
+-----------+ +-------------+
簡短來說奈惑,響應(yīng)一個用戶請求時吭净,將用戶信息和授權(quán)范圍序列化后放入一個JSON字符串,然后使用Base64進(jìn)行編碼肴甸,最終在授權(quán)服務(wù)器用私鑰
對這個字符串進(jìn)行簽名寂殉,得到一個JSON Web Token
,我們可以像使用Access Token
一樣的直接使用它原在,假設(shè)其他所有的資源服務(wù)器都將持有一個RSA公鑰友扰。當(dāng)資源服務(wù)器接收到這個在Http Header中存有Token
的請求,資源服務(wù)器就可以拿到這個Token
庶柿,并驗證它是否使用正確的私鑰簽名(是否經(jīng)過授權(quán)服務(wù)器簽名村怪,也就是驗簽
)。驗簽
通過浮庐,反序列化后就拿到OAuth 2
的驗證信息甚负。
驗證服務(wù)器返回的信息可以是以下內(nèi)容:
- access_token - 訪問令牌,用于資源訪問
- refresh_token - 當(dāng)訪問令牌失效审残,使用這個令牌重新獲取訪問令牌
- token_type - 令牌類型梭域,這里是
Bearer
也就是基本HTTP認(rèn)證 - expire_in - 過期時間
- jti - JWT ID
由于Access Token
是Base64編碼
,反編碼后就是下面的格式搅轿,標(biāo)準(zhǔn)的JWT格式病涨。也就是Header
、 Payload
璧坟、Signature
三部分既穆。
{
"alg":"RS256",
"typ":"JWT"
}
{
"exp": 1492873315,
"user_name": "reader",
"authorities": [
"AURH_READ"
],
"jti": "8f2d40eb-0d75-44df-a8cc-8c37320e3548",
"client_id": "web_app",
"scope": [
"FOO"
]
}
&:l?s)???-[??+
F"2"K???8??:u9??? 9Q?32Z??$ec{3mxJ?h??0D?F庖?[?!?N)?knVV?V|夻??E??}??f9>'<蕱?B?е?o?v虀D?8C?4???K}Em?? YVcaqIW&*u?u??b!?*?\?-{??X??WTq
使用JWT可以簡單的傳輸Token
赎懦,用RSA簽名
保證Token
很難被偽造。Access Token
字符串中包含用戶信息和權(quán)限范圍幻工,我們所需的全部信息都有了励两,所以不需要維護(hù)Token
存儲,資源服務(wù)器也不必要求Token
檢查囊颅。
+-----------+ +-----------+
| | 1-Request Resource | |
| |----------------------------------->| |
| | Authorization: bearer Access Token | |--+
| | | Resource | | 2-Verify
| Client | | Service | | Token
| | 3-Response Resource | |<-+
| |<-----------------------------------| Public Key|
| | | |
+-----------+ +-----------+
所以伐蒋,在微服務(wù)中使用OAuth 2
,不會影響到整體架構(gòu)的可擴(kuò)展性迁酸。淡然這里還有一些問題沒有涉及先鱼,例如Access Token
過期后,使用Refresh Token
到認(rèn)證服務(wù)器重新獲取Access Token
等奸鬓,后面會有具體的例子來展開討論這些問題焙畔。
如果您感興趣,后面還會有實現(xiàn)部分串远,敬請期待~
由于 http://asciiflow.com/ 流程圖使用中文就無法對齊了宏多,本文中流程圖都是英文了~