開發者必備知識 - HTTP認證(HTTP Authentication)

2018/11/18 posted in  Security comments

認證(Authentication)是建構Web應用或Web伺服器不可或缺的一環。以下簡單介紹一下各種常見的HTTP Authentication方式。

總覽 Overview

參照MDN - HTTP authentication

業界標準

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的流行下,網站會想

  1. 借助它們來方便user登入自己的網站
  2. 代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(長時效的)

流程圖:

UmvA7
Image Source

IV. OAuth 2.0

OAuth 2.0建基於1.0,簡化了流程,並更好地支援App或SPA開發。

認證流程

3-legged簡化為2-legged,Consumer不用再獲取Request Token。

流程圖:

Xn4c0
Image Source

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 )

Source

例子

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裡面。

17
Image Source

潛在問題:Replay Attack

JWT沒有nonce在裡面,attacker可以原封不動地重複傳送request,server會照樣認為是valid request。

解決方案:

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。