編者注:今天我們分享的是盧士傑同學整理的網站常用鑑權方案的實現原理與實現以及他們的適用場景,幫助大家在業務中做合適的選擇。
背景
説起鑑權大家應該都很熟悉,不過作為前端開發來講,鑑權的流程大頭都在後端小哥那邊,本文的目的就是為了讓大家瞭解一下常見的鑑權的方式和原理。
認知:HTTP 是一個無狀態協議,所以客户端每次發出請求時,下一次請求無法得知上一次請求所包含的狀態數據。
一、HTTP Auth Authentication
簡介
HTTP 提供一個用於權限控制和認證的通用框架。最常用的HTTP認證方案是HTTP Basic Authentication
鑑權流程
加解密過程
// Authorization 加密過程
let email = "postmail@test.com"
let password = "12345678"
let auth = `${email}:${password}`
const buf = Buffer.from(auth, 'ascii');
console.info(buf.toString('base64')); // cG9zdG1haWxAdGVzdC5jb206MTIzNDU2Nzg=
// Authorization 解密過程
const buf = Buffer.from(authorization.split(' ')[1] || ''), 'base64');
const user = buf.toString('ascii').split(':');
其他 HTTP 認證
通用 HTTP 身份驗證框架有多個驗證方案使用。不同的驗證方案會在安全強度上有所不同。
IANA 維護了一系列的驗證方案,除此之外還有其他類型的驗證方案由虛擬主機服務提供,例如 Amazon AWS ,常見的驗證方案包括:
- Basic (查看 RFC 7617, Base64 編碼憑證. 詳情請參閲下文.),
- Bearer (查看 RFC 6750, bearer 令牌通過OAuth 2.0保護資源),
- Digest (查看 RFC 7616, 只有 md5 散列 在Firefox中支持, 查看 bug 472823 用於SHA加密支持),
- HOBA (查看 RFC 7486 (草案), HTTP Origin-Bound 認證, 基於數字簽名),
- Mutual (查看 draft-ietf-httpauth-mutual),
- AWS4-HMAC-SHA256 (查看 AWS docs)
二、Cookie + Session
註冊流程
思考:為什麼要在密碼里加點“鹽”?
鑑權流程
Session 存儲
最常用的 Session 存儲方式是 KV 存儲,如Redis,在分佈式、API 支持、性能方面都是比較好的,除此之外還有 mysql、file 存儲。
如果服務是分佈式的,使用 file 存儲,多個服務間存在同步 session 的問題;高併發情況下錯誤讀寫鎖的控制。
Session Refresh
我們上面提到的流程中,缺少 Session 的刷新的環節,我們不能在用户登錄之後經過一個 expires 時間就把用户踢出去,如果在 Session 有效期間用户一直在操作,這時候 expires 時間就應該刷新。
以 Koa 為例,刷新 Session 的機制也比較簡單:
開發一個 middleware(默認情況下所有請求都會經過該 middleware),如果校驗 Session 有效,就更新 Session 的 expires: 當前時間+過期時間。
優化:
- 頻繁更新 session 會影響性能,可以在 session 快過期的時候再更新過期時間。
- 如果某個用户一直在操作,同一個 sessionID 可能會長期有效,如果相關 cookie 泄露,可能導致比較大的風險,可以在生成 sessionID 的同時生成一個 refreshID,在 sessionID 過期之後使用 refreshID 請求服務端生成新的 sessionID(這個方案需要前端判斷 sessionID 失效,並攜帶 refreshID 發請求)。
單設備登錄
有些情況下,只允許一個帳號在一個端下登錄,如果換了一個端,需要把之前登錄的端踢下線(默認情況下,同一個帳號可以在不同的端下同時登錄的)。
這時候可以藉助一個服務保存用户唯一標識和 sessionId 值的對應關係,如果同一個用户,但 sessionId 不一樣,則不允許登錄或者把之前的踢下線(刪除舊 session )。
三、JWT
簡介
JSON Web Token (JWT)是一個開放標準(RFC 7519),它定義了一種緊湊的、自包含的方式,用於作為JSON對象在各方之間安全地傳輸信息。該信息可以被驗證和信任,因為它是數字簽名的。
JWT 組成
JWT 由三部分組成,分別是 header(頭部),payload(載荷),signature(簽證) 這三部分以小數點連接起來。
例如使用名為 jwt-token 的cookie來存儲 JWT 例如:
jwt-token=eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJuYW1lIjoibHVzaGlqaWUiLCJpYXQiOjE1MzI1OTUyNTUsImV4cCI6MTUzMjU5NTI3MH0.WZ9_poToN9llFFUfkswcpTljRDjF4JfZcmqYS0JcKO8;
使用.分割值可以得到三部分組成元素,按照順序分別為:
-
header:- 值:eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9
- Base64 解碼:
{"alg": "HS256", "type": "JWT"}
-
payload:- 值:eyJuYW1lIjoibHVzaGlqaWUiLCJpYXQiOjE1MzI1OTUyNTUsImV4cCI6MTUzMjU5NTI3MH0
-
Base64 解碼:
{ "name": "lushijie", "iat": 1532595255, // 發佈時間 "exp": 1532595270 // 過期時間 }
-
signature:- 值:WZ9_poToN9llFFUfkswcpTljRDjF4JfZcmqYS0JcKO8
-
解碼:
const headerEncode = base64Encode(header); const payloadEncode = base64Encode(payload); let signature = HMACSHA256(headerEncode + '.' + payloadEncode, '密鑰');
鑑權流程
Token 校驗
對於驗證一個 JWT 是否有效也是比較簡單的,服務端根據前面介紹的計算方法計算出 signature,和要校驗的JWT中的 signature 部分進行對比就可以了,如果 signature 部分相等則是一個有效的 JWT。
Token Refresh
為了減少 JWT Token 泄露風險,一般有效期會設置的比較短。 這樣就會存在 JWT Token 過期的情況,我們不可能讓用户頻繁去登錄獲取新的 JWT Token。
解決方案:
可以同時生成 JWT Token 與 Refresh Token,其中 Refresh Roken 的有效時間長於 JWT Token,這樣當 JWT Token 過期之後,使用 Refresh Token 獲取新的 JWT Token 與 Refresh Token,其中 Refresh Token 只能使用一次。
四、OAuth
簡介
有時候,我們登錄某個網站,但我們又不想註冊該網站的賬號,這時我們可以使用第三方賬號登錄,比如 github、微博、微信、QQ等。
開放授權(OAuth)是一個開放標準,允許用户讓第三方應用訪問該用户在某一網站上存儲的私密的資源(如照片,視頻,聯繫人列表),而無需將用户名和密碼提供給第三方應用。OAuth允許用户提供一個令牌,而不是用户名和密碼來訪問他們存放在特定服務提供者的數據。每一個令牌授權一個特定的網站(例如,視頻編輯網站)在特定的時段(例如,接下來的2小時內)內訪問特定的資源(例如僅僅是某一相冊中的視頻)。這樣,OAuth讓用户可以授權第三方網站訪問他們存儲在另外服務提供者的某些特定信息,而非所有內容。
OAuth是OpenID的一個補充,但是完全不同的服務。
—— 摘自 維基百科
授權流程
名詞解釋:
- Third-party application:第三方應用程序又稱"客户端"(client),比如打開知乎,使用第三方登錄,選擇 Github 登錄,這時候知乎就是客户端。
- Resource Owner:資源所有者,本文中又稱"用户"(user),即登錄用户。
- Authorization server:認證服務器,即 Github 專門用來處理認證的服務器。
- Resource server:資源服務器,即 Github 存放用户生成的資源的服務器。它與認證服務器,可以是同一台服務器,也可以是不同的服務器。
- A. A網站讓用户跳轉到 GitHub,請求授權碼;GitHub 要求用户登錄,然後詢問“知乎網站要求獲得 xx 權限,你是否同意?”;
- B. 用户同意,GitHub 就會重定向回 A 網站,同時發回一個授權碼;
- C. A 網站使用授權碼,向 GitHub 請求令牌;
- D. GitHub 返回令牌;
- E. A 網站使用令牌,向 GitHub 請求用户數據;
其他授權模式
授權碼模式(authorization code)是功能最完整、流程最嚴密的授權模式。除了我們上面所説的授權碼模式,其實還有其他授權模式:
- 簡化模式(Implicit grant type)
有些 Web 應用是純前端應用,沒有後端。這時就不能用上面的方式了,必須將令牌儲存在前端。RFC 6749 就規定了第二種方式,允許直接向前端頒發令牌。這種方式沒有授權碼這個中間步驟 - 密碼模式(Resource Owner Password Credentials Grant)
如果你高度信任某個應用,RFC 6749 也允許用户把用户名和密碼,直接告訴該應用。該應用就使用你的密碼,申請令牌 - 客户端模式(Client Credentials Grant)
適用於沒有前端的命令行應用,即在命令行下請求令牌
關於這些模式詳細請見:OAuth2.0 的四種方式
單點登錄
單點登錄(Single Sign On, SSO),即:單一標記(單點)登錄。例如:QQ,我在QQ空間登錄一次,我可以去訪問QQ產品的其他服務:QQ郵箱、騰訊新聞等,都能保證你的賬户保持登錄狀態。
延伸閲讀:
- 《如何實現單點登錄? 》
- 《手機掃碼登錄內網怎麼實現的?》
五、總結對比
沒有最好,只有最合適!!!
-
HTTP Auth Authentication:
- 梳理總結:
通用 HTTP 身份驗證框架有多個驗證方案使用。不同的驗證方案會在安全強度上有所不同。HTTP Auth Authentication 是最常用的 HTTP認證方案,為了減少泄露風險一般要求 HTTPS 協議。 - 適用場景:
一般多被用在內部安全性要求不高的的系統上,如路由器網頁管理接口 -
問題:
- 請求上攜帶驗證信息,容易被嗅探到
- 無法註銷
- 梳理總結:
-
Cookie + Session:
-
梳理總結:
- 服務端存儲 session ,客户端存儲 cookie,其中 cookie 保存的為 sessionID
- 可以靈活 revoke 權限,更新信息後可以方便的同步 session 中相應內容
- 分佈式 session 一般使用 redis(或其他KV) 存儲
- 使用場景:
適合傳統系統獨立鑑權
-
-
JWT:
-
梳理總結:
- 服務器不再需要存儲 session,服務器認證鑑權業務可以方便擴展
- JWT 並不依賴 cookie,也可以使用 header 傳遞
- 為減少盜用,要使用 HTTPS 協議傳輸
-
適用場景:
- 適合做簡單的 RESTful API 認證
- 適合一次性驗證,例如註冊激活鏈接
-
問題:
- 使用過程中無法廢棄某個 token,有效期內 token 一直有效
- payload 信息更新時,已下發的 token 無法同步
-
-
OAuth:
-
梳理總結:
- OAuth是一個開放標準,允許用户授權第三方應用訪問他們存儲在另外的服務提供者上的信息,而不需要將用户名和密碼提供給第三方移動應用或分享他們數據的所有內容。
- GitHub OAuth 文檔 Identifying and authorizing users for GitHub Apps
-
適用場景:
OAuth 分為下面四種模式- 簡化模式,不安全,適用於純靜態頁面應用
- 授權碼模式,功能最完整、流程最嚴密的授權模式,通常使用在公網的開放平台中
- 密碼模式,一般在內部系統中使用,調用者是以用户為單位。
- 客户端模式,一般在內部系統之間的 API 調用。兩個平台之間調用,以平台為單位。
-