在一些互聯網公司的面試中,面試官每每會問這樣一個問題:html
「若是禁用瀏覽器 cookie,如何實現用戶追蹤和認證?」web
遺憾的是依然有大量候選人答非所問,沒法搞清楚 cookie 和 session 之間的區別。而在工做中也有讓人驚訝的真實案例:把 user ID 存儲到 local storage 中當作 token 使用,緣由是他們聲稱棄用了 cookie 這種落後的東西;一個移動端項目,服務器給出的 API 中須要客戶端模擬一個 cookie,從而像瀏覽器中 ajax 那樣消費 API。面試
互聯網是基於 HTTP 協議構建的,而 HTTP 協議由於簡單流行開來,可是 HTTP 協議是無狀態(通訊層面上虛電路比數據報昂貴太多)的,爲此人們爲了追蹤用戶想出了各類辦法,包括 cookie/session 機制、token、flash 跨瀏覽器 cookie 甚至瀏覽器指紋等。ajax
把用戶身份藏在每個地方(瀏覽器指紋技術甚至不須要存儲介質)算法
講使用 spring security 等具體技術的資料已經不少了,這篇文章不打算寫框架和代碼的具體實現。咱們會討論認證和受權的區別,而後會介紹一些被業界普遍採用的技術,最後會聊聊怎麼爲 API 構建選擇合適的認證方式。spring
首先,認證和受權是兩個不一樣的概念,爲了讓咱們的 API 更加安全和具備清晰的設計,理解認證和受權的不一樣就很是有必要了,它們在英文中也是不一樣的單詞。數據庫
認證是 authentication,指的是當前用戶的身份,當用戶登錄事後系統便能追蹤到他的身份作出符合相應業務邏輯的操做。即便用戶沒有登陸,大多數系統也會追蹤他的身份,只是當作來賓或者匿名用戶來處理。認證技術解決的是 「我是誰?」的問題。json
受權則不一樣,受權是 authorization,指的是什麼樣的身份被容許訪問某些資源,在獲取到用戶身份後繼續檢查用戶的權限。單一的系統受權每每是伴隨認證來完成的,可是在開放 API 的多系統結構下,受權能夠由不一樣的系統來完成,例如 OAuth。受權技術是解決「我能作什麼?」的問題。瀏覽器
實現認證和受權的基礎是須要一種媒介(credentials)來標記訪問者的身份或權利,在現實生活中每一個人都須要一張身份證才能訪問本身的銀行帳戶、結婚和辦理養老保險等,這就是認證的憑證;在古代軍事活動中,皇帝會給出戰的將軍頒發兵符,下級將領不關心持有兵符的人,只須要執行兵符對應的命令便可。在互聯網世界中,服務器爲每個訪問者頒發 session ID 存放到 cookie,這就是一種憑證技術。數字憑證還表如今方方面面,SSH 登陸的密匙、JWT 令牌、一次性密碼等。緩存
用戶帳戶也不必定是存放在數據庫中的一張表,在一些企業 IT 系統中,對帳戶管理和權限有了更多的要求。因此帳戶技術 (accounting)能夠幫助咱們使用不一樣的方式管理用戶帳戶,同時具備不一樣系統之間共享帳戶的能力。例如微軟的活動目錄(AD),以及簡單目錄訪問協議(LDAP),甚至區塊鏈技術。
還有一個重要的概念是訪問控制策略(AC)。若是咱們須要把資源的權限劃分到一個很細的粒度,就不得不考慮用戶以何種身份來訪問受限的資源,選擇基於訪問控制列表(ACL)仍是基於用戶角色的訪問控制(RBAC)或者其餘訪問控制策略。
在流行的技術和框架中,這些概念都沒法孤立的被實現,所以在現實中使用這些技術時,你們每每爲一個 OAuth2 是認證仍是受權這種概念爭論不休。爲了容易理解,我在文末附上了一份常見技術和概念的術語表。下面我會介紹在API開發中經常使用的幾種認證和受權技術:HTTP Basic AUthentication、HAMC、OAuth2,以及憑證技術JWT token。
你必定用過這種方式,但不必定知道它是什麼,在不久以前,當你訪問一臺家用路由器的管理界面,每每會看到一個瀏覽器彈出表單,要求你輸入用戶密碼。
在這背後,當用戶輸入完用戶名密碼後,瀏覽器幫你作了一個很是簡單的操做:
組合用戶名和密碼而後 Base64 編碼
給編碼後的字符串添加 Basic 前綴,而後設置名稱爲 Authorization 的 header 頭部
API 也能夠很是簡單的提供 HTTP Basic Authentication 認證方式,那麼客戶端能夠很簡單經過 Base64 傳輸用戶名和密碼便可:
這種方式實現起來很是簡單,在大量場景下被採用。固然缺點也很明顯,Base64 只能稱爲編碼,而不是加密 (實際上無需配置密匙的客戶端並無任何可靠地加密方式,咱們都依賴 TSL 協議)。這種方式的致命弱點是編碼後的密碼若是明文傳輸則容易在網絡傳輸中泄露,在密碼不會過時的狀況下,密碼一旦泄露,只能經過修改密碼的方式。
在咱們對接一些 PASS 平臺和支付平臺時,會要求咱們預先生成一個 access key(AK) 和 secure key(SK),而後經過簽名的方式完成認證請求,這種方式能夠避免傳輸 secure key,且大多數狀況下簽名只容許使用一次,避免了重放攻擊。
這種基於 AK/SK 的認證方式主要是利用散列的消息認證碼 (Hash-based MessageAuthentication Code) 來實現的,所以有不少地方叫 HMAC 認證,實際上不是很是準確。HMAC 只是利用帶有 key 值的哈希算法生成消息摘要,在設計 API 時有具體不一樣的實現。
HMAC 在做爲網絡通訊的認證設計中做爲憑證生成算法使用,避免了口令等敏感信息在網絡中傳輸。基本過程以下:
爲了讓每一次請求的簽名變得獨一無二,從而實現重放攻擊,咱們須要在簽名時放入一些干擾信息。
在業界標準中有兩種典型的作法,質疑/應答算法(OCRA: OATH Challenge-Response Algorithm)、基於時間的一次性密碼算法(TOTP:Time-based One-time Password Algorithm)。
質疑/應答算法須要客戶端先請求一次服務器,得到一個 401 未認證的返回,並獲得一個隨機字符串(nonce)。將 nonce 附加到按照上面說到的方法進行 HMAC 簽名,服務器使用預先分配的 nonce 一樣進行簽名校驗,這個 nonce 在服務器只會被使用一次,所以能夠提供惟一的摘要。
爲了不額外的請求來獲取 nonce,還有一種算法是使用時間戳,而且經過同步時間的方式協商到一致,在必定的時間窗口內有效(1分鐘左右)。
這裏的只是利用時間戳做爲驗證的時間窗口,並不能嚴格的算做基於時間的一次性密碼算法。標準的基於時間的一次性密碼算法在兩步驗證中被大量使用,例如 Google 身份驗證器不須要網絡通訊也能實現驗證(但依賴準確的授時服務)。原理是客戶端服務器共享密鑰而後根據時間窗口能經過 HMAC 算法計算出一個相同的驗證碼。
TOTP 基本原理和常見廠商
OAuth(開放受權)是一個開放標準,容許用戶受權第三方網站訪問他們存儲在另外的服務提供者上的信息,而不須要將用戶名和密碼提供給第三方網站或分享他們數據的全部內容。
OAuth 是一個受權標準,而不是認證標準。提供資源的服務器不須要知道確切的用戶身份(session),只須要驗證受權服務器授予的權限(token)便可。
上圖只是 OAuth 的一個簡化流程,OAuth 的基本思路就是經過受權服務器獲取 access token 和 refresh token(refresh token 用於從新刷新access token),而後經過 access token 從資源服務器獲取數據 。在特定的場景下還有下面幾種模式:
若是須要獲取用戶的認證信息,OAuth 自己沒有定義這部份內容,若是須要識別用戶信息,則須要藉助另外的認證層,例如 OpenID Connect。
在一些介紹OAuth 的博客中不多講到資源服務器是怎麼驗證 access token 的。OAuth core 標準並無定義這部分,不過在 OAuth 其餘標準文件中提到兩種驗證 access token的方式。
幾乎全部人剛開始瞭解 OAuth 時都有一個一疑問,爲何已經有了 access token 還須要 refresh token 呢?
受權服務器會在第一次受權請求時一塊兒返回 access token 和refresh token,在後面刷新 access token 時只須要 refresh token。 access token 和 refresh token 的設計意圖是不同的,access token 被設計用來客戶端和資源服務器之間交互,而 refresh token 是被設計用來客戶端和受權服務器之間交互。
某些受權模式下 access token 須要暴露給瀏覽器,充當一個資源服務器和瀏覽器之間的臨時會話,瀏覽器和資源服務器之間不存在簽名機制,access token 成爲惟一憑證,所以 access token 的過時時間(TTL)應該儘可能短,從而避免用戶的 access token 被嗅探攻擊。
因爲要求 access token 時間很短,refresh token 能夠幫助用戶維護一個較長時間的狀態,避免頻繁從新受權。你們會以爲讓 access token 保持一個長的過時時間不就能夠了嗎?實際上 refresh token 和 access token 的不一樣之處在於即便 refresh token 被截獲,系統依然是安全的,客戶端拿着 refresh token 去獲取 access token 時同時須要預先配置的 secure key,客戶端和受權服務器以前始終存在安全的認證。
認證方面的術語實在太多,我在搭建本身的認證服務器或接入第三方認證平臺時,有時候到完成開發工做的最後一刻都沒法理解這些術語。
OAuth 負責解決分佈式系統之間的受權問題,即便有時候客戶端和資源服務器或者認證服務器存在同一臺機器上。OAuth 沒有解決認證的問題,但提供了良好的設計利於和現有的認證系統對接。
Open ID 解決的問題是分佈式系統之間身份認證問題,使用Open ID token 能在多個系統之間驗證用戶,以及返回用戶信息,能夠獨立使用,與 OAuth 沒有關聯。
OpenID Connect 解決的是在 OAuth 這套體系下的用戶認證問題,實現的基本原理是將用戶的認證信息(ID token)當作資源處理。在 OAuth 框架下完成受權後,再經過 access token 獲取用戶的身份。
這三個概念之間的關係有點難以理解,用現實場景來講,若是系統中須要一套獨立的認證系統,並不須要多系統之間的受權能夠直接採用 Open ID。若是使用了 OAuth 做爲受權標準,能夠再經過 OpenID Connect 來完成用戶的認證。
在 OAuth 等分佈式的認證、受權體系下,對憑證技術有了更多的要求,好比包含用戶 ID、過時等信息,不須要再外部存儲中關聯。所以業界對 token 作了進一步優化,設計了一種自包含令牌,令牌簽發後無需從服務器存儲中檢查是否合法,經過解析令牌就能獲取令牌的過時、有效等信息,這就是JWT (JSON Web Token)。
JWT 是一種包含令牌(self-contained token),或者叫值令牌 (value token),咱們之前使用關聯到 session 上的 hash 值被叫作引用令牌(reference token)。
簡而言之,一個基本的JWT令牌爲一段點分3段式結構。
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiYWRtaW4iOnRydWV9.TJVA95OrM7E2cBab30RMHrHDcEfxjoYZgeFONFh7HgQ
生成JWT 令牌的流程爲
所以只須要簽名的 secret key 就能校驗 JWT 令牌,若是在消息體中加入用戶 ID、過時信息就能夠實現驗證令牌是否有效、過時了,無需從數據庫/緩存中讀取信息。由於使用了加密算法,因此第1、二部分即便被修改(包括過時信息)也沒法經過驗證。JWT 優勢是不只能夠做爲 token 使用,同時也能夠承載一些必要信息,省去屢次查詢。
注意:
JWT token 在微服務的系統中優點特別突出。多層調用的 API 中能夠直接傳遞 JWT token,利用自包含的能力,能夠減小用戶信息查詢次數;更重要的是,使用非對稱的加密方式能夠經過在系統中分發密匙的方式驗證 JWT token。
固然 OAuth 對 access token 等憑證所選用的技術並無作出限制,OAuth 並不強制使用 JWT,在使用 JWT 自包含特性的優點時,必須考慮到 JWT 撤回困難的問題。在一些對撤回 token 要求很高的項目中不適合使用JWT,即便採用了一些方案實現(whitelist 和 blacklist)也違背了設計 JWT 的初衷。
在構建 API 時,開發者會發現咱們的認證方式和網頁應用有一些不一樣,除了像 ajax 這種典型的 web 技術外,若是咱們但願 API 是無狀態的,不推薦使用 Cookie。
使用 Cookie 的本質是用戶第一次訪問時服務器會分配一個 Session ID,後面的請求中客戶端都會帶上這個 ID 做爲當前用戶的標誌,由於 HTTP 自己是無狀態的,Cookie 屬於一種內建於瀏覽器中實現狀態的方式。若是咱們的 API 是用來給客戶端使用的,強行要求 API 的調用者管理Cookie 也能夠完成任務。
在一些遺留或者不是標準的認證明現的項目中,咱們依然能夠看到這些作法,快速地實現認證。
隨着微服務的發展,API 的設計不只僅是面向 WEB 或者 Mobile APP,還有BFF(Backend for Frontend)和 Domain API 的認證,以及第三方服務的集成。
客戶端到服務器之間認證和服務器到服務器之間認證是不一樣的。
咱們把終端用戶(Human)參與的通訊,叫作 Human-to-machine (H2M),服務器與服務器之間的通訊叫作 Machine-to-machine (M2M)。
H2M 的通訊須要更高的安全性,M2M 的通訊自然比 H2M 安全,所以更多的強調性能,在不一樣的場合下選擇合適的認證技術就顯得特別重要。例如 HTTP Basic Authentication 用來做爲 H2M 認證顯得有些落後,可是在 M2M 中被大量使用。
另外值得一提的是,H2M 這種通訊方式下,客戶端不受控制,因爲沒法自主分發密匙,認證通訊的安全高度依賴 HTTPS。
從一個宏觀的角度看待他們的關係,對咱們技術選型很是有幫助。
[HMAC: Keyed-Hashing for Message Authentication]( "www.ietf.org/rfc/rfc2104… ")
HOTP: An HMAC-Based One-Time Password Algorithm
OCRA: OATH Challenge-Response Algorithm
The OAuth 2.0 Authorization Framework
Internet-Draft Archive for OAuth
文/ThoughtWorks林寧
更多精彩洞見,請關注微信公衆號:ThoughtWorks洞見