前言
在上集的最後我們提到 Session-based authentication,是一種 stateful 的驗證機制,然而在 API-based 的架構中,這種驗證機制反而成為了限制,為了突破這個限制,我們需要採用另一種 stateless 的驗證機制,也就是這篇文章所談的 Token-based authentication。
目錄
- 什麼是 Token
- Token-based Authentication 的運作過程
- HTTP 常見的認證標準
- 如何產生 Token: JWT 標準
- JWT、JWS 和 JWE 之間的關係
Token-based Authentication
什麼是 Token
Tokenization, when applied to data security, is the process of substituting a sensitive data element with a non-sensitive equivalent, referred to as a token, that has no extrinsic or exploitable meaning or value. — wiki
簡單地說,將一組敏感且有意義的資訊轉成一組沒意義的資訊,此時這組沒意義的資訊就叫做 Token。
而 Token 主要的用途驗證權限,透過解析 Token 的亂數可以查明你是否有權限行使某些行為,在現實生活中,Token 常被用於門禁系統中,假設今天你是某社區的住戶,社區委員會總不能要求你每次進入社區時都要用身份證表明身份,這樣會有個資外洩的風險,於是當委員會在驗證你是社區住戶後,會發給你一張磁卡 (i.e token),此磁卡本身裡面是一串亂數,即使遺失了也不用怕個資外洩。
在 Token 驗證系統中,確保 Token 來源是一個重要的措施,假設沒有這項措施,就有可能使用A社區的磁卡進入B社區,驗證 Token 來源需要使用一個加密技術,雜湊函數 (Hash),常見的雜湊演算法有 SHA-1, SHA-2 和 SHA-256。
雜湊函數可以將一組隨機長度的字串轉換成一組固定長度的雜湊值(測試網站),雜湊函數有兩個重要的特性:
- 不同的字串不會產生相同的雜湊值
- 雜湊值不可逆向轉回原本的值
雜湊函數常被使用於驗證資料的完整性(i.e 資料是否被竄改過),由於相同的資料通過相同的雜湊函數會產生相同的雜湊值,一旦資料被竄改雜湊值也會更改,所以說雜湊值可以當作資料的完整性簽章 (Signature)。
Token 驗證系統中,我們可以簡單地在 Token 資訊後面加上專屬亂數值,然後將整組資訊透過雜湊函數產生一組雜湊值並在 Token 之後,隨後每當系統接收 Token 以及其雜湊值 ,就能透過此雜湊值來檢驗這個 Token 的來源。
Token-based Authentication 的運作過程
Token-based authentication 又可稱作 stateless authentication,原因在於 server-side 只負責產生 Token 並不儲存任何用戶資訊,所有用戶資訊以及Token 皆儲存在 Client-side (i.e LocalStorage, SessionStorage, Cookies)。
因為 Token 可以作為 JSON data 跨平台的傳輸(e.g 手機或 IoT),Token-based authentication 適合 API-based 的架構,
壞處就是 server-side 失去了對 session 的掌控權,例如 server-side 無法註銷特定用戶的 token 直到 token 自己過期。
在上述 Token-based authentication 的流程,當 Client-side 收到 Token 後,在隨後的 request,都會將 Token 放在HTTP 的 Authorization header 中透過 HTTP 的驗證機制進行授權:
首先當 client 要求的資源需要認證時, server-side 會回傳一個 401 的 response,並在 WWW-Authentication 的 header 中放入認證機制的標準,隨後 client-side 會在 request Authorization 的 header 放入認證標準以及認證資訊,之後 server-side 進行驗證通過回傳 200 的 response 反之 403 response。
HTTP 常見的認證標準
在上述的流程會發現,在Authorization header 必須指定認證標準,以下就簡單介紹一下 HTTP 常見的認證標準:
1.Basic Authentication: 顧名思義最簡單的認證方法,將 username 和 password 通過 base64 編碼後產生亂碼最為認證資訊。
username = 'vic1234'
password = 'abcd1234'basic_auth = Base64.encode64("#{username}:#{passowrd}")
request.set_header('Authorization', "Basic #{basic_auth}")
缺點:
- 一旦 HTTP 封包被攔截,username 和 password 可輕易被解碼。
- 無法避免 replay attack
2.Digest Authentication: 為了避免資訊外洩以及 replay attack,Digest authentication 使用 nonce( i.e number once) 和 雜湊函數演算法來驗證使用者的身份是否正確。
簡單地說,server-side 會在 WWW-Authenticate 的 header 中加入以下資訊:
- nonce: 只能使用一次的隨機亂數。
- algorithm: 雜湊的演算法類型,通常為 MD5。
client-side 會根據 digest 的規則產生的雜湊值,並在 Authorization 的 header 中加入:
- username: 用戶名稱
- nonce: 從 server-side 接收的一次性亂數。
- cnonce: 從 client-side 產生的 nonce。
- uri: 用戶所求的資源識別碼 (e.g URL)。
- response: 雜湊值。
當 server-side 接收這些資訊就可透過相同的規則產生雜湊值,並以此來驗證用戶身份 (完整的 digest value 產生過程) 。
雖然 nonce 可避免 replay attack 和 chosen-plaintext attack,但壞處就是 client-side 必須多發一次 request 去獲取 nonce。
3.OAuth 1.0: 為了提高網路理論的效應,許多網路服務會透過 API 來分享彼此資源,在分享資源前,網路服務也必須取得其他服務的授權才能存取其資源,例如使用者要透過 IG 直接在 FB 發文,IG 必須先要求使用者輸入 FB 帳號進行驗證並授權,若使用 Digest 的授權方式,使用者帳號資訊在驗證後仍會在不同服務平台上傳遞,這會有資安疑慮(詳細的資安疑慮),為了解決這個問題 OAuth 1.0 (i.e Open Authorization )就誕生了。
簡單地說,OAuth 1.0 將認證身分的 User Credential (e.g username, password) 和實際執行權限的 Access token 分開,讓其他服務在認證後直接使用 token 進行資源存取,從而避免 User Credential 曝露在其他服務的風險 (詳細的授權流程)。
4.OAuth 2.0: 由於 OAuth 1.0 的取得 Token 的流程稍微複雜,OAuth 2.0 簡化了 OAuth 1.0 的流程並將其 Token 稱為 Bearer Token。
OAuth 1.0:
Authorization: OAuth oauth_consumer_key="cChZNFj6T5R0TigYB9yd1w",
oauth_nonce="a9900fe68e2573b27a37f10fbad6a755",
oauth_signature="39cipBtIOHEEnybAR4sATQTpl2I%3D",
oauth_signature_method="HMAC-SHA1",
oauth_timestamp="1318467427",
oauth_token="NPcudxy0yU5T3tBzho7iCotZ3cnetKwcTIRlX0iwRl0",
oauth_version="1.0"
OAuth 2.0:
Authorization : Bearer cn389ncoiwuencr
若想知道OAuth 2.0 詳細的使用說明,可到 RFC 6750 官方文件中查看,此文件的第一章到第四張說明了獲取 Bearer Token的流程,如何發送帶有 Bearer Token 的 request,WWW-Authentication Header 中有哪些屬性以及 Access Token Response 的格式。
如何產生 Token: JWT 標準
RFC 6750 中只有定義 Token 的使用方法,並沒有明確規定產生 Token 的方法,所以對於 Token 的產生,關鍵是要 server-side 能夠驗證且夠安全,常見的 Token 產生方法是 JWT 。
JSON Web Token (JWT) is a compact claims representation format intended for space constrained environments such as HTTP Authorization headers and URI query parameters.
簡單地說,JWT 是一總 JOSN 格式可用於壓縮 JSON Object 和空間限制的環境中,例如 HTTP Authorization header 或 query parameters 中。
JWT 的格式主要有三個元素組成:
1.JOSE Header: 全名為 Javascript Object Signing and Encryption,也就是用來定義是否要 Sign 和 Encrpy 這個 JWT 資料,其結構為:
{
"typ": "jwt",
"alg": "HS256"
}
2.Payload: 可以解釋為要壓縮的資料,將 JWT 用於 Authentication 中 Payload 可以為 user_id:
{ "user_id": "b08f86af-35da-48f2-8fab-cef3904660bd" }
3.Signature: 用來驗證資料完整性的雜湊值,產生雜湊值的方法為:
header = { typ: 'jwt', alg: 'HS256' }
payload = { user_id: "b08f86af-35da-48f2-8fab-cef3904660bd" }encoded_header = Base64.urlsafe_encode64(header.to_json)
encoded_payload = Base64.urlsafe_encode64(payload.to_json)
data = "#{encoded_header}.#{encoded_payload}"key = 'some_secret'
signature = mac = OpenSSL::HMAC.hexdigest("SHA256", key, data)encoded_signature = Base64.urlsafe_encode64(signature)
最後用 “.” 去串連這三個編碼過的元素,就是實做 JWT 壓縮出來的結果:
token = "#{encoded_header}.#{encoded_payload}.#{encoded_signature}"
JWT、JWS 和 JWE 之間的關係
當提到 JWT 與 Token-based authentication時,時常會看到 JWS 和 JWE ,首先簡單地看一下這三個的全名:
JWT 如上所述,是一個 JSON Object 資料壓縮的格式,竟然只是格式的話,JWT 就並不能代表 Token 本身,因為 Token 是遵循這個格式所產生的結果。
而真正的 Token 其實是 JWS 或 JWE,而這兩個有什麼差別呢?其實從字面上就能看得出來,一個是依造 JWT 格式並使用了雜湊演算法所產生的 Token,另一個則是使用 非對稱式加密演算法 所產生的 Token。
我們可以透過在 JOSE 中的 alg 屬性中設定不同的演算法來產生 JWS token 或 JWE token,在 RFC 文件中(JWS、JWE)分別定義了兩種 Token 所使用的演算法。
最後在這裡下個結論:
- Token-based authentication 的存在是為了能在 API-based 的設計架構中實踐驗證與授權機制當提到 JWT 與 Token-based authentication時,時常會看到 JWS 和 JWE ,首先簡單地看一下這三個的全名:
- Token-based authentication 的過程實做上會使用的 HTTP Authorization 的 header 以及 OAuth 2.0 (i.e Bearer) 驗證規範
- JWT 是一個壓縮 JSON Object 資料的格式,JWS 和 JWE 是用不同演算法來實做 JWT 的兩個方法,若我們將 User Credential 的資訊放入 JWT 的 payload 並以此來產生 Token,此時這個 Token 就能結合 OAuth 2.0 來完成 Token-based authentication 的功能。
下一篇文章主要是討論 Session-based 和 Token-based authentication 的關係,以及 JWT 的一些問題。