使用 OAuth 2 和 JWT 為微服務提供安全保障 – 基本概念

freewolf發表於2019-03-01

Part 1 – 理論相關

作者 freewolf

關鍵詞

微服務Spring CloudOAuth 2.0JWTSpring SecuritySSOUAA

寫在前面

作為從業了十多年的IT行業和程式的老司機,今天如果你說你不懂微服務,都不好意思說自己的做軟體的。SOA喊了多年,無人不知,但又有多少系統開發真正的SOA了呢?但是好像一夜之間所有人都投入了微服務的懷抱。

作為目前最主流的“微服務框架”,Spring Cloud發展速度很快,成為了最全面的微服務解決方案。不管什麼軟體體系,什麼框架,安全永遠是不可能繞開的話題,我也把它作為我最近一段時間研究微服務的開篇。

老話題!“如何才能在微服務體系中保證安全?”,為了達成目標,這裡採用一個簡單而可行方式來保護Spring Cloud中服務的安全,也就是建立統一的使用者授權中心。

這裡補充說一下什麼是Authentication(認證)Authorization(鑑權),其實很簡單,認證關心你是誰,鑑權關心你能幹什麼。舉個大家一致都再說的例子,如果你去機場乘機,你持有的護照代表你的身份,這是認證,你的機票就是你的許可權,你能幹什麼。

學習微服務並不是一個簡單的探索過程,這不得學習很多新的知識,其實不管是按照DDD(Domain Driven Design)領域驅動設計中領域模型的方式,還是將微服務拆分成更小的粒度。都會遇到很多新的問題和以前一直都沒解決很好的問題。隨著不斷的思考,隨著熟悉Facebook/GitHub/AWS這些機構是如何保護內部資源,答案也逐漸浮出水面。

為了高效的實現這個目標,這裡採用OAuth 2JWT(JSON Web Tokens)技術作為解決方案,

為什麼使用OAuth 2

儘管微服務在現代軟體開發中還算一個新鮮事物,但是OAuth 2已經是一個廣泛使用的授權技術,它讓Web開發者在自己提供服務中,用一種安全的方式直訪問Google/Facebook/GitHub平臺使用者資訊。但在我開始闡述細節之前,我將揭開聚焦到本文真正的主題:雲安全

那麼在雲服務中對使用者訪問資源的控制,我們一般都怎麼做呢?然我舉一些大家似乎都用過的但又不是很完美的例子。

我們可以設定邊界伺服器或者帶認證功能的反向代理伺服器,假設所有訪問請求都發給它。通過認證後,轉發給內部相應的伺服器。一般在Spring MVC Security開發中幾乎都會這樣做的。但這並不安全,最重要的是,一旦是有人從內部攻擊,你的資料毫無安全性。

其他方式:我們為所有服務建立統一的許可權資料庫,並在每次請求前對使用者進行鑑權,聽起來某些方面的確有點愚蠢,但實際上這確實是一個可行的安全方案。

更好的方式: 使用者通過授權服務來實現鑑權,把使用者訪問Session對映成一個Token。所有遠端訪問資源伺服器相關的API必須提供Token。然後資源伺服器訪問授權服務來識別Token,得知Token屬於哪個使用者,並瞭解通過這個Token可以訪問什麼資源。

這聽起來是個不錯的方案,對不?但是如何保證Token的安全傳輸?如何區分是使用者訪問還是其他服務訪問?這肯定是我們關心的問題。

所以上述種種問題讓我們選擇使用OAuth 2,其實訪問Facebook/Google的敏感資料和訪問我們自己後端受保護資料沒什麼區別,並且他們已經使用這樣的解決方案很多年,我們只要遵循這些方法就好了。

OAuth 2是如何工作的

如果你瞭解OAuth 2相關的原理,那麼在部署OAuth 2是非常容易的。
讓我們描述下這樣一個場景,“某App希望獲得TomFacebook上相關的資料”

OAuth 2 在整個流程中有四種角色:

  • 資源擁有者(Resource Owner) – 這裡是Tom
  • 資源伺服器(Resource Server) – 這裡是Facebook
  • 授權伺服器(Authorization Server) – 這裡當然還是Facebook,因為Facebook有相關資料
  • 客戶端(Client) – 這裡是某App

Tom試圖登入Facebook某App將他重定向到Facebook的授權伺服器,當Tom登入成功,並且許可自己的Email和個人資訊被某App獲取。這兩個資源被定義成一個Scope(許可權範圍),一旦准許,某App的開發者就可以申請訪問許可權範圍中定義的這兩個資源。

+--------+                               +---------------+
|        |--(A)- Authorization Request ->|   Resource    |
|        |                               |     Owner     |
|        |<-(b)-- authorization="" grant="" ---|="" |="" +---------------+="" |--(c)--="" --="">| Authorization |
| Client |                               |     Server    |
|        |<-(d)----- access="" token="" -------|="" |="" +---------------+="" |--(e)-----="" ------="">|    Resource   |
|        |                               |     Server    |
|        |<-(f)--- protected="" resource="" ---|="" |="" +--------+="" +---------------+<="" code="">複製程式碼

Tom允許了許可權請求,再次通過重定向返回某App,重定向返回時攜帶了一個Access Token(訪問令牌),接下來某App就可以通過這個Access TokenFacebook直接獲取相關的授權資源(也就是Email和個人資訊),而無需重新做Tom相關的鑑權。而且每當Tom登入了某App,都可以通過之前獲得的Access Token,直接獲取相關授權資源。

到目前為止,我們如何直接將以上內容用於實際的例子中?OAuth 2十分友好,並容易部署,所有互動都是關於客戶端和許可權範圍的。

  • OAuth 2中的客戶端許可權範圍和我們平時的使用者和許可權是否相同?
  • 我需要將授權對映到許可權範圍中或將使用者對映到客戶端中?
  • 為什麼我需要客戶端?

你也許在之前在類似的企業級開發案例中嘗試對映過相關的角色。這會很棘手!

任何型別的應用都提供使用者登入,登入結果是一個Access Token,所有的之後的API呼叫都將這個Access Token加入HTTP請求頭中,被呼叫服務去授權伺服器驗證Access Token並獲取該Token可訪問的許可權資訊。這樣一來,所有服務的訪問都會請求另外的服務來完成鑑權。

許可權範圍和角色,客戶端和使用者

OAuth 2中,你可以定義哪個應用(網站、移動客戶端、桌面應用、其他)可以訪問那些資源。這裡只有一個尺寸,來自哪裡的哪個使用者可以訪問那些資料,當然也是哪個應用或者服務可以訪問那些資源。換一種說法,許可權範圍就是控制那些端點對客戶端可見,或者使用者根據他的許可權來獲取相關的資料。

在一個線上商店中,前端可以看做一個客戶端,可以訪問商品、訂單和客戶資訊,但後端可以關於物流和合同等,另一方面,使用者可以訪問一個服務但並不是全部的資料,這可以是因為使用者正在使用Web應用,當他不能的時候,其他使用者卻可以。服務之間的訪問時我們要討論的另一個維度。如果你熟悉數學,我可以說在OAuth 2中,客戶端-許可權範圍關係是線性獨立於使用者-許可權關係。

為什麼是JWT

OAuth 2並不關心去哪找Access Token和把它存在什麼地方的,生成隨機字串並儲存Token相關的資料到這些字串中儲存好。通過一個令牌端點,其他服務可能會關心這個Token是否有效,它可以通過哪些許可權。這就是使用者資訊URL方法,授權伺服器為了獲取使用者資訊轉換為資源伺服器。

當我們談及微服務時,我們需要找一個Token儲存的方式,來保證授權伺服器可以被水平擴充套件,儘管這是一個很複雜的任務。所有訪問微服務資源的請求都在Http Header中攜帶Token,被訪問的服務接下來再去請求授權伺服器驗證Token的有效性,目前這種方式,我們需要兩次或者更多次的請求,但這是為了安全性也沒什麼其他辦法。但擴充套件Token儲存會很大影響我們系統的可擴充套件性,這是我們引入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="" +-----------+="" +-------------+<="" code="">複製程式碼

簡短來說,響應一個使用者請求時,將使用者資訊和授權範圍序列化後放入一個JSON字串,然後使用Base64進行編碼,最終在授權伺服器用私鑰對這個字串進行簽名,得到一個JSON Web Token,我們可以像使用Access Token一樣的直接使用它,假設其他所有的資源伺服器都將持有一個RSA公鑰。當資源伺服器接收到這個在Http Header中存有Token的請求,資源伺服器就可以拿到這個Token,並驗證它是否使用正確的私鑰簽名(是否經過授權伺服器簽名,也就是驗籤)。驗籤通過,反序列化後就拿到OAuth 2的驗證資訊。

驗證伺服器返回的資訊可以是以下內容:

  • access_token – 訪問令牌,用於資源訪問
  • refresh_token – 當訪問令牌失效,使用這個令牌重新獲取訪問令牌
  • token_type – 令牌型別,這裡是Bearer也就是基本HTTP認證
  • expire_in – 過期時間
  • jti – JWT ID

由於Access TokenBase64編碼,反編碼後就是下面的格式,標準的JWT格式。也就是HeaderPayloadSignature三部分。

{ 
  "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"8ۓٞ:u9ٴ̯ޡ 9Q32Zƌ޿$ec{3mxJh0DF庖[!뀭N)㥔knVVĖV|夻ׄE㍫}Ŝf9>`<蕱굤Bۋеϵov虀DӨ8C4K}Emޢ    YVcaqIW&*uʝub!׏*Ť՟-{ʖX܌WTq複製程式碼

使用JWT可以簡單的傳輸Token,用RSA簽名保證Token很難被偽造。Access Token字串中包含使用者資訊和許可權範圍,我們所需的全部資訊都有了,所以不需要維護Token儲存,資源伺服器也不必要求Token檢查。

+-----------+                                    +-----------+
|           |       1-Request Resource           |           |
|           |----------------------------------->|           |
|           | Authorization: bearer Access Token |           |--+
|           |                                    | Resource  |  | 2-Verify
|  Client   |                                    | Service   |  |  Token
|           |       3-Response Resource          |           |<-+ |="" |<-----------------------------------|="" public="" key|="" +-----------+="" +-----------+<="" code="">複製程式碼

所以,在微服務中使用OAuth 2,不會影響到整體架構的可擴充套件性。淡然這裡還有一些問題沒有涉及,例如Access Token過期後,使用Refresh Token到認證伺服器重新獲取Access Token等,後面會有具體的例子來展開討論這些問題。

如果您感興趣,後面還會有實現部分,敬請期待~

由於 asciiflow.com/ 流程圖使用中文就無法對齊了,本文中流程圖都是英文了~

相關文章