認證(Authentication)是建構Web應用或Web伺服器不可或缺的一環。以下簡單介紹一下各種常見的HTTP Authentication方式。
總覽 Overview
- Basic: RFC 2617 (1999) -> RFC 7617 (2015)
- Digest: RFC 2069 (1997) -> RFC 2617 (1999) -> RFC 7617 (2015)
- OAuth 1.0 (Twitter, 2007)
- OAuth 2.0 (2012)
- Bearer (OAuth 2.0): RFC 6750 (2012)
- JSON Web Tokens (JWT): RFC 7519 (2015)
業界標準
W3C在 RFC 1945: HTTP/1.0 (1996) 中,定義了HTTP架構的認證方式。
Server - 認證提示
如果Client傳送了未認證的request,Server會回應:
HTTP 401 Unauthorized
WWW-Authenticate: <type> realm="xxx" ...
例如:
WWW-Authenticate: Basic realm="User Visible Realm"
Client - 認證
Client會在request header加入以下資料來認證自己:
Authorization: <type> <credentials>
- type:認證方式,例如Basic, Digest...
- credentials:與type相符的密碼
例如:
Authorization: Basic YWxpY2U6c3VwZXJtYW4=
I. Basic Authentication
由username和password來組成認證碼,例如:
- username:
alice
, pw:superman
alice:superman
- Base64 encoded:
YWxpY2U6c3VwZXJtYW4=
- HTTP request header:
Authorization: Basic YWxpY2U6c3VwZXJtYW4=
Basic故名思義,是最簡單方便快捷的方法,user甚至不用經過login page來登入。
問題
- Server只能確認帳號密碼,無法控制例如登入時效(session),只能靠browser的cookie expiration
- Attacker可以Decode header並得到username和password
- Replay attack
於是又有了比較複雜一點的方法:Digest。
II. Digest Authentication
以下是Digest的密鑰產生方法:
HA1 = MD5(username:realm:password)
HA2 = MD5(method:digestURI)
response = MD5(HA1:nonce:HA2)
實際使用
Server返回401 Unauthorized
:
HTTP/1.0 401 Unauthorized
WWW-Authenticate: Digest realm="testrealm@host.com",
qop="auth,auth-int",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
然後User計算一下Digest的response
再傳到server:
Authorization: Digest username="Mufasa",
realm="testrealm@host.com",
nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093",
uri="/dir/index.html",
qop=auth,
nc=00000001,
cnonce="0a4f113b",
response="6629fae49393a05397450978507c4ef1",
opaque="5ccc069c403ebaf9f0171e9517f40e41"
Wikipedia - Digest Authentication
好處
- 可以加入其他資訊,如URI可以控制File-level的存取
- 加入Nonce,防止了Chosen plaintext attack和Replay attack
問題
- 要先取得nonce,所以需要多一個Request
- Server依然不能控制user session
III. OAuth 1.0
Basic和Digest同樣是以user的帳號密碼來組成Credential,然後交給網站認證身份。但在Google、Facebook、Twitter的流行下,網站會想
- 借助它們來方便user登入自己的網站
- 代user在這些平台執行某些動作(如發帖)
於是便誕生了OAuth架構。
比起Basic和Digest,OAuth主要的不同,是分開了用來認證身份的User credential(如username、password)和實際執行權限的Access token。此概念容許了「以Facebook登入」等等的應用,而且是安全地。
認證流程
OAuth流程牽涉三個持分者:
- User
- Consumer
- 一般是你正在使用的網站或App
- Server / Service Provider
- Facebook、Twitter等網站
OAuth使用俗稱「3-legged OAuth」,分別代表三個階段,以下用Twitter作為例子:
- 前設
- 網站已在Twitter注冊了自己是Consumer
- Temporary Credentials Acquisition
- 網站從Twitter獲得未認證的Request Token(暫時性)
- Authorization
- Redirect user到Twitter
- User登入並認證之前的Request Token
- Redirect user回到網站
- Token Exchange
- 網站把Request Token傳到Twitter
- 取得Access Token(長時效的)
流程圖:
- Implementing Sign in with Twitter
- Twitter API Authentication - OAuth 1.0
- OAuth - what it is and how it works
IV. OAuth 2.0
OAuth 2.0建基於1.0,簡化了流程,並更好地支援App或SPA開發。
認證流程
3-legged簡化為2-legged,Consumer不用再獲取Request Token。
流程圖:
Bearer Token
OAuth 2.0使用Bearer Token作為認證後的Access token,大大簡化了Request header的結構。
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
看著都感到精神爽利。
不同於Basic和Digest,Bearer Token並沒有規定的建立方法,只要最後server能verify就可以了。
V. JSON Web Tokens (JWT)
Basic和Digest token是由username、password等等資訊來構成,而JWT也相似。不同的是:
- JWT可以存更多(任意)資訊
- 採用Hash、Signature的驗證方法
- 加入Server的
secret
,由Server生產Token,Client不會知道如何建立這token
Token組成方式
JWT由兩部分組成,由.
串連起來,然後用Secret hash起來,成為Signature:
data = base64urlEncode( header ) + “.” + base64urlEncode( payload )
hashedData = hash( data, secret )
signature = base64urlEncode( hashedData )
例子
Header: 有關JWT的資料
{
"alg": "HS256",
"typ": "JWT"
}
Payload: 可以存起任意用戶資料
{
"sub": "1234567890",
"name": "John Doe",
"admin": true
}
Secret:
KKF2QT4fwpMeJf36POk6yJV_adQssw5c
結果:
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.tImCzvIkqaNmGB5mMAG1DZRnZO56sjoYO5nU2YUdRK4
上文說過Bearer沒有硬性的Algorithm規定,只要server能verify就可以了。JWT也可以是其中一種Bearer Token。事實上JWT一般都會使用Bearer
為前綴。
認證流程
流程非常簡單,基本上就是一次Login Request,從Server取得JWT,就成功認證了。
之後Server只需自己Hash一次,就能分辨用戶傳來的JWT是否正確,十分方便用在App裡面。
潛在問題:Replay Attack
JWT沒有nonce在裡面,attacker可以原封不動地重複傳送request,server會照樣認為是valid request。
解決方案:
- JWT提出了使用JWT ID (JTI)加上黑名單的方法(雖然是打破了stateless的前設)
- 在JWT外再自己加上一個nonce系統
Auth0寫了一篇JWT攻擊和防範,想深入研究一下JWT的話值得一看。
其他Token-based Authentication
JWT是一種Token-based Authentication,其實還有很多使用Token來認證的方法,例如Django REST Framework的TokenAuthentication,基本上都是Login到Server,然後取得Access token。
總結
這篇文章我們簡介了:
- Basic
- Digest
- OAuth 1.0
- OAuth 2.0
- JWT
整個發展歷程,可以總結為:
由依賴Browser的功能和RFC標準,發展成更簡單、更"Separation of concerns"的方式(直接一個request + 一個token),更好的支援Mobile和Single-page Application。