從單體應用架構到分佈式應用架構再到微服務架構,應用的安全訪問在不斷的經受考驗。爲了適應架構的變化、需求的變化,身份認證與鑑權方案也在不斷的變革。面對數十個甚至上百個微服務之間的調用,如何保證高效安全的身份認證?面對外部的服務訪問,該如何提供細粒度的鑑權方案?本文將會爲你們闡述微服務架構下的安全認證與鑑權方案。web
單體應用 VS 微服務算法
隨着微服務架構的興起,傳統的單體應用場景下的身份認證和鑑權面臨的挑戰愈來愈大。單體應用體系下,應用是一個總體,通常針對全部的請求都會進行權限校驗。請求通常會經過一個權限的攔截器進行權限的校驗,在登陸時將用戶信息緩存到 session 中,後續訪問則從緩存中獲取用戶信息。數據庫
而微服務架構下,一個應用會被拆分紅若干個微應用,每一個微應用都須要對訪問進行鑑權,每一個微應用都須要明確當前訪問用戶(autheticate)以及其權限(authorize)。尤爲當訪問來源不僅是瀏覽器,還包括其餘服務的調用時,單體應用架構下的鑑權方式就不是特別合適了。在爲服務架構下,要考慮外部應用接入的場景、用戶 - 服務的鑑權、服務 - 服務的鑑權等多種鑑權場景。瀏覽器
David Borsos 在倫敦的微服務大會上提出了四種方案:緩存
1. 單點登陸(SSO)安全
此方案意味着每一個面向用戶的服務都必須與認證服務交互,會產生大量很是瑣碎的網絡流量和重複的工做,當動輒數十個微應用時,這種方案的弊端會更加明顯。服務器
2. 分佈式 Session 方案微信
分佈式會話方案原理主要是將關於用戶認證的信息存儲在共享存儲中,且一般由用戶會話做爲 key 來實現的簡單分佈式哈希映射。當用戶訪問微服務時,用戶數據能夠從共享存儲中獲取。在某些場景下,這種方案很不錯,用戶登陸狀態是不透明的。同時也是一個高可用且可擴展的解決方案。這種方案的缺點在於共享存儲須要必定保護機制,所以須要經過安全連接來訪問,這時解決方案的實現就一般具備至關高的複雜性了。網絡
3. 客戶端 Token 方案session
令牌在客戶端生成,由身份驗證服務進行簽名,而且必須包含足夠的信息,以即可以在全部微服務中創建用戶身份。令牌會附加到每一個請求上,爲微服務提供用戶身份驗證,這種解決方案的安全性相對較好,但身份驗證註銷是一個大問題,緩解這種狀況的方法可使用短時間令牌和頻繁檢查認證服務等。對於客戶端令牌的編碼方案,Borsos 更喜歡使用 JSON Web Tokens(JWT),它足夠簡單且庫支持程度也比較好。
4. 客戶端 Token 與 API 網關結合
這個方案意味着全部請求都經過網關,從而有效地隱藏了微服務。 在請求時,網關將原始用戶令牌轉換爲內部會話 ID 令牌。在這種狀況下,註銷就不是問題,由於網關能夠在註銷時撤銷用戶的令牌。
微服務常見安全認證方案 HTTP 基本認證
- HTTP Basic Authentication(HTTP 基本認證)是 HTTP 1.0 提出的一種認證機制,這個想必你們都很熟悉了,我再也不贅述。HTTP 基本認證的過程以下:
-
客戶端發送 HTTP Request 給服務器。
-
由於 Request 中沒有包含 Authorization header,服務器會返回一個 401 Unauthozied 給客戶端,而且在 Response 的 Header "WWW-Authenticate" 中添加信息。
-
客戶端把用戶名和密碼用 BASE64 加密後,放在 Authorization Header 中發送給服務器, 認證成功。(注意:這一步的表述有問題,BASE64不是加密手段,而是編碼手段)
-
服務器將 Authorization Header 中的用戶名密碼取出,進行驗證, 若是驗證經過,將根據請求,發送資源給客戶端。
- 基於 Session 的認證
基於 Session 的認證應該是最經常使用的一種認證機制了。用戶登陸認證成功後,將用戶相關數據存儲到 Session 中,單體應用架構中,默認 Session 會存儲在應用服務器中,而且將 Session ID 返回到客戶端,存儲在瀏覽器的 Cookie 中。
可是在分佈式架構下,Session 存放於某個具體的應用服務器中天然就沒法知足使用了,簡單的能夠經過 Session 複製或者 Session 粘制的方案來解決。
Session 複製依賴於應用服務器,須要應用服務器有 Session 複製能力,不過如今大部分應用服務器如 Tomcat、JBoss、WebSphere 等都已經提供了這個能力。
除此以外,Session 複製的一大缺陷在於當節點數比較多時,大量的 Session 數據複製會佔用較多網絡資源。Session 粘滯是經過負載均衡器,將統一用戶的請求都分發到固定的服務器節點上,這樣就保證了對某一用戶而言,Session 數據始終是正確的。不過這種方案依賴於負載均衡器,而且只能知足水平擴展的集羣場景,沒法知足應用分割後的分佈式場景。
在微服務架構下,每一個微服務拆分的粒度會很細,而且不僅有用戶和微服務打交道,更多還有微服務間的調用。這個時候上述兩個方案都沒法知足,就要求必需要將 Session 從應用服務器中剝離出來,存放在外部進行集中管理。能夠是數據庫,也能夠是分佈式緩存,如 Memchached、Redis 等(這個是常見的解決方案)。
- 基於 Token 的認證
隨着 Restful API、微服務的興起,基於 Token 的認證如今已經愈來愈廣泛。Token 和 Session ID 不一樣,並不是只是一個 key。Token 通常會包含用戶的相關信息,經過驗證 Token 就能夠完成身份校驗。像 Twitter、微信、QQ、GitHub 等公有服務的 API 都是基於這種方式進行認證的,一些開發框架如 OpenStack、Kubernetes 內部 API 調用也是基於 Token 的認證。基於 Token 認證的一個典型流程以下:
-
用戶輸入登陸信息(或者調用 Token 接口,傳入用戶信息),發送到身份認證服務進行認證(身份認證服務能夠和服務端在一塊兒,也能夠分離,看微服務拆分狀況了)。
-
身份驗證服務驗證登陸信息是否正確,返回接口(通常接口中會包含用戶基礎信息、權限範圍、有效時間等信息),客戶端存儲接口,能夠存儲在 Session 或者數據庫中。
-
用戶將 Token 放在 HTTP 請求頭中,發起相關 API 調用。
-
被調用的微服務,驗證 Token 權限。
-
服務端返回相關資源和數據。
基於 Token 認證的好處以下:
-
服務端無狀態:Token 機制在服務端不須要存儲 session 信息,由於 Token 自身包含了全部用戶的相關信息。
-
性能較好,由於在驗證 Token 時不用再去訪問數據庫或者遠程服務進行權限校驗,天然能夠提高很多性能。
-
支持移動設備。
-
支持跨程序調用,Cookie 是不容許垮域訪問的,而 Token 則不存在這個問題。
下面會重點介紹兩種基於 Token 的認證方案 JWT/Oauth2.0。
- JWT 介紹
JSON Web Token(JWT)是爲了在網絡應用環境間傳遞聲明而執行的一種基於 JSON 的開放標準(RFC 7519)。來自 JWT RFC 7519 標準化的摘要說明:JSON Web Token 是一種緊湊的,URL 安全的方式,表示要在雙方之間傳輸的聲明。JWT 通常被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源,也能夠增長一些額外的其它業務邏輯所必須的聲明信息,該 Token 也可直接被用於認證,也可被加密。
JWT 認證流程
-
客戶端調用登陸接口(或者獲取 token 接口),傳入用戶名密碼。
-
服務端請求身份認證中心,確認用戶名密碼正確。
-
服務端建立 JWT,返回給客戶端。
-
客戶端拿到 JWT,進行存儲(能夠存儲在緩存中,也能夠存儲在數據庫中,若是是瀏覽器,能夠存儲在 Cookie 中)在後續請求中,在 HTTP 請求頭中加上 JWT。
-
服務端校驗 JWT,校驗經過後,返回相關資源和數據。
JWT 結構
JWT 是由三段信息構成的,第一段爲頭部(Header),第二段爲載荷(Payload),第三段爲簽名(Signature)。每一段內容都是一個 JSON 對象,將每一段 JSON 對象採用 BASE64 編碼,將編碼後的內容用. 連接一塊兒就構成了 JWT 字符串。以下:
header.payload.signature
1. 頭部(Header)
頭部用於描述關於該 JWT 的最基本的信息,例如其類型以及簽名所用的算法等。這也能夠被表示成一個 JSON 對象。
{ "typ": "JWT", "alg": "HS256" }
在頭部指明瞭簽名算法是 HS256 算法。
2. 載荷(payload)
載荷就是存放有效信息的地方。有效信息包含三個部分:
-
標準中註冊的聲明
-
公共的聲明
-
私有的聲明
標準中註冊的聲明(建議但不強制使用):
-
iss:JWT 簽發者
-
sub:JWT 所面向的用戶
-
aud:接收 JWT 的一方
-
exp:JWT 的過時時間,這個過時時間必需要大於簽發時間
-
nbf:定義在什麼時間以前,該 JWT 都是不可用的
-
iat:JWT 的簽發時間
-
jti:JWT 的惟一身份標識,主要用來做爲一次性 token, 從而回避重放攻擊。
公共的聲明 :
公共的聲明能夠添加任何的信息,通常添加用戶的相關信息或其餘業務須要的必要信息. 但不建議添加敏感信息,由於該部分在客戶端可解密。
私有的聲明 :
私有聲明是提供者和消費者所共同定義的聲明,通常不建議存放敏感信息,由於 base64 是對稱解密的,意味着該部分信息能夠歸類爲明文信息。
示例以下:
{ "iss": "Online JWT Builder", "iat": 1416797419, "exp": 1448333419, "aud": "www.primeton.com", "sub": "devops@primeton.com", "GivenName": "dragon", "Surname": "wang", "admin": true }
3. 簽名(signature)
建立簽名須要使用 Base64 編碼後的 header 和 payload 以及一個祕鑰。將 base64 加密後的 header 和 base64 加密後的 payload 使用. 鏈接組成的字符串,經過 header 中聲明的加密方式進行加鹽 secret 組合加密,而後就構成了 jwt 的第三部分。
好比:HMACSHA256(base64UrlEncode(header) + "." + base64UrlEncode(payload), secret)
JWT 的優勢:
-
跨語言,JSON 的格式保證了跨語言的支撐
-
基於 Token,無狀態
-
佔用字節小,便於傳輸
關於 Token 註銷:
Token 的註銷,因爲 Token 不存儲在服務端,由客戶端存儲,當用戶註銷時,Token 的有效時間尚未到,仍是有效的。因此如何在用戶註銷登陸時讓 Token 註銷是一個要關注的點。通常有以下幾種方式:
-
Token 存儲在 Cookie 中,這樣客戶端註銷時,天然能夠清空掉
-
註銷時,將 Token 存放到分佈式緩存中,每次校驗 Token 時區檢查下該 Token 是否已註銷。不過這樣也就失去了快速校驗 Token 的優勢。
-
多采用短時間令牌,好比令牌有效期是 20 分鐘,這樣能夠必定程度上下降註銷後 Token 可用性的風險。
OAuth 2.0 介紹
OAuth 的官網介紹:An open protocol to allow secure API authorization in a simple and standard method from desktop and web applications。OAuth 是一種開放的協議,爲桌面程序或者基於 BS 的 web 應用提供了一種簡單的,標準的方式去訪問須要用戶受權的 API 服務。OAUTH 認證受權具備如下特色:
-
簡單:無論是 OAuth 服務提供者仍是應用開發者,都很容易於理解與使用;
-
安全:沒有涉及到用戶密鑰等信息,更安全更靈活;
-
開放:任何服務提供商均可以實現 OAuth,任何軟件開發商均可以使用 OAuth;
OAuth 2.0 是 OAuth 協議的下一版本,但不向後兼容 OAuth 1.0,即徹底廢止了 OAuth 1.0。 OAuth 2.0 關注客戶端開發者的簡易性。要麼經過組織在資源擁有者和 HTTP 服務商之間的被批准的交互動做表明用戶,要麼容許第三方應用表明用戶得到訪問的權限。同時爲 Web 應用,桌面應用和手機,和起居室設備提供專門的認證流程。2012 年 10 月,OAuth 2.0 協議正式發佈爲 RFC 6749。
受權流程
OAuth 2.0 的流程以下:
(A)用戶打開客戶端之後,客戶端要求用戶給予受權。(B)用戶贊成給予客戶端受權。(C)客戶端使用上一步得到的受權,向認證服務器申請令牌。(D)認證服務器對客戶端進行認證之後,確認無誤,贊成發放令牌。(E)客戶端使用令牌,向資源服務器申請獲取資源。(F)資源服務器確認令牌無誤,贊成向客戶端開放資源。
四大角色
由受權流程圖中能夠看到 OAuth 2.0 有四個角色:客戶端、資源擁有者、資源服務器、受權服務器。
-
客戶端:客戶端是表明資源全部者對資源服務器發出訪問受保護資源請求的應用程序。
-
資源擁有者:資源擁有者是對資源具備受權能力的人。
-
資源服務器:資源所在的服務器。
-
受權服務器:爲客戶端應用程序提供不一樣的 Token,能夠和資源服務器在統一服務器上,也能夠獨立出去。
客戶端的受權模式
客戶端必須獲得用戶的受權(Authorization Grant),才能得到令牌(access token)。OAuth 2.0 定義了四種受權方式:authorizationcode、implicit、resource owner password credentials、client credentials。
1. 受權碼模式(authorization code)
受權碼模式(authorization code)是功能最完整、流程最嚴密的受權模式。它的特色就是經過客戶端的後臺服務器,與"服務提供商"的認證服務器進行互動。流程以下:
-
用戶訪問客戶端,後者將前者導向認證服務器。
-
用戶選擇是否給予客戶端受權。
-
假設用戶給予受權,認證服務器將用戶導向客戶端事先指定的"重定向 URI"(redirection URI),同時附上一個受權碼。
-
客戶端收到受權碼,附上早先的"重定向 URI",向認證服務器申請令牌。這一步是在客戶端的後臺的服務器上完成的,對用戶不可見。
-
認證服務器覈對了受權碼和重定向 URI,確認無誤後,向客戶端發送訪問令牌(access token)和更新令牌(refresh token)。
2. 簡化模式(implicit)
簡化模式(Implicit Grant Type)不經過第三方應用程序的服務器,直接在瀏覽器中向認證服務器申請令牌,跳過了"受權碼"這個步驟,所以得名。全部步驟在瀏覽器中完成,令牌對訪問者是可見的,且客戶端不須要認證。流程以下:
-
客戶端將用戶導向認證服務器。
-
用戶決定是否給於客戶端受權。
-
假設用戶給予受權,認證服務器將用戶導向客戶端指定的"重定向 URI",並在 URI 的 Hash 部分包含了訪問令牌。
-
瀏覽器向資源服務器發出請求,其中不包括上一步收到的 Hash 值。
-
資源服務器返回一個網頁,其中包含的代碼能夠獲取 Hash 值中的令牌。
-
瀏覽器執行上一步得到的腳本,提取出令牌。
-
瀏覽器將令牌發給客戶端。
3. 密碼模式(Resource Owner Password Credentials)
密碼模式中,用戶向客戶端提供本身的用戶名和密碼。客戶端使用這些信息,向"服務商提供商"索要受權。在這種模式中,用戶必須把本身的密碼給客戶端,可是客戶端不得儲存密碼。這一般用在用戶對客戶端高度信任的狀況下,好比客戶端是操做系統的一部分,或者由一個著名公司出品。而認證服務器只有在其餘受權模式沒法執行的狀況下,才能考慮使用這種模式。流程以下:
-
用戶向客戶端提供用戶名和密碼。
-
客戶端將用戶名和密碼發給認證服務器,向後者請求令牌。
-
認證服務器確認無誤後,向客戶端提供訪問令牌。
4. 客戶端模式(Client Credentials)
客戶端模式(Client Credentials Grant)指客戶端以本身的名義,而不是以用戶的名義,向"服務提供商"進行認證。嚴格地說,客戶端模式並不屬於 OAuth 框架所要解決的問題。
在這種模式中,用戶直接向客戶端註冊,客戶端以本身的名義要求"服務提供商"提供服務,其實不存在受權問題。流程以下:
-
客戶端向認證服務器進行身份認證,並要求一個訪問令牌。
-
認證服務器確認無誤後,向客戶端提供訪問令牌。
思考總結
正如 David Borsos 所建議的一種方案,在微服務架構下,咱們更傾向於將 Oauth 和 JWT 結合使用,Oauth 通常用於第三方接入的場景,管理對外的權限,因此比較適合和 API 網關結合,針對於外部的訪問進行鑑權(固然,底層 Token 標準採用 JWT 也是能夠的)。JWT 更加輕巧,在微服務之間進行訪問鑑權已然足夠,而且能夠避免在流轉過程當中和身份認證服務打交道。固然,從能力實現角度來講,相似於分佈式 Session 在不少場景下也是徹底能知足需求,具體怎麼去選擇鑑權方案,仍是要結合實際的需求來。