一、Token Auth機制
基於Token的身份驗證是無狀態的,我們不將用戶信息存在服務器或Session中。
相比原始的Cookie+Session方式,更適合分布式系統的用戶認證,繞開了傳統的分布式Session一致性等問題。
基於Token的身份驗證的主流程如下:
用戶通過用戶名和密碼發送請求;
程序驗證;
程序返回一個簽名的token 給客戶端;
客戶端儲存token,並且每次用於每次發送請求。
二、相比Cookie認證的優勢
支持跨域跨站點訪問:
Cookie是不允許垮域訪問的,可以通過設置頂級域名的方式實現部分跨域,但是跨站點的訪問仍然不支持,
如果使用Token機制,就可以通過HTTP頭傳輸用戶認證信息,從而更好的實現跨域跨站點。
無狀態:
Token機制在服務端不需要存儲session信息,Token自身包含了登錄用戶的信息,只需要在客戶端的cookie或本地介質存儲狀態信息;
去耦:不需要綁定到一個特定的身份驗證方案。Token可以在任何地方生成,只要在你的API被調用的時候,你可以進行Token生成調用即可;
更適用於移動應用:
當客戶端是原生應用時,Cookie是不被支持的,雖然目前Webview的方式可以解決Cookie問題,
但是顯然采用Token認證機制會簡單得多;
安全性更強:
因為不再依賴於Cookie,所以你就不需要考慮對CSRF(跨站請求偽造)的防范;
標准化易擴展:
可以采用標准化的 JSON Web Token (JWT),對以后系統接入Node等純前端開發更便捷;
相比Session一致性提高性能:
相比服務端保存Session一致性信息,並查詢用戶登錄狀態,一般來說Token的驗證過程(包含加密和解密),性能開銷會更小。
三、JSON Web Token標准的設計
JWT 標准的 Token 有三個部分:
header.payload.signature
三個部分中間用點分隔開,並且都使用 Base64 編碼,所以生成的 Token 類似這樣:
ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9.ewogImlzcyI6ICJjaGJsb2dzLmNvbSIsCiAiZXhwIjogIjE0NzA3MzAxODIiLAogInVpZCI6ICIxMjM0NWFiY2RlIiwKfQ.9q2eq8sa374ao2uq9607r6qu6
(1)Header報頭
header 部分主要包括兩部分,一個是 Token 的類型,另一個是使用的算法,
比如下面類型就是 JWT,使用的算法是 HS256。
{
"typ": "JWT",
"alg": "HS256"
}
Header內容要用 Base64 的形式編碼,所以就變成這樣:
ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9
(2)Payload載荷部分
Payload 里面是 Token 的具體內容,這部分內容可以自定義,JWT有標准字段,也可以添加其它需要的內容。
標准字段:
iss:Issuer,發行者
sub:Subject,主題
aud:Audience,觀眾
exp:Expiration time,過期時間
nbf:Not before
iat:Issued at,發行時間
jti:JWT ID
這是一個典型的payload信息,包含了發行者(網站)、過期時間和用戶id:
{
"iss": "chblogs.com",
"exp": "1470730182",
"uid": "12345abcde",
}
這部分內容同樣要用Base64 編碼,生成編碼類似如下格式:
ewogImlzcyI6ICJjaGJsb2dzLmNvbSIsCiAiZXhwIjogIjE0NzA3MzAxODIiLAogInVpZCI6ICIxMjM0NWFiY2RlIiwKfQ==
(3)Signature簽名部分
簽名部分主要和token的安全性有關,Signature的生成依賴前面兩部分。
首先將Base64編碼后的Header和Payload用.連接在一起,
ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9.ewogImlzcyI6ICJjaGJsb2dzLmNvbSIsCiAiZXhwIjogIjE0NzA3MzAxODIiLAogInVpZCI6ICIxMjM0NWFiY2RlIiwKfQ
對這個字符串使用HmacSHA256算法進行加密,這個密鑰secret存儲在服務端,前端不可見,
String str="ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9." + "ewogImlzcyI6ICJjaGJsb2dzLmNvbSIsCiAiZXhwIjogIjE0NzA3MzAxODIiLAogInVpZCI6ICIxMjM0NWFiY2RlIiwKfQ"; byte[] inputData = str.getBytes(); String key = Coder.initMacKey(); BigInteger sha = new BigInteger(Coder.encryptHMAC(inputData, key)); System.out.println("HS256加密后——"+sha.toString(32));
下面使用密鑰 THISSHA 進行加密:
9q2eq8sa374ao2uq9607r6qu6
然后將Signature和前面兩部分拼接起來,得到最后的token:
ewogICJ0eXAiOiAiSldUIiwKICAiYWxnIjogIkhTMjU2Igp9.ewogImlzcyI6ICJjaGJsb2dzLmNvbSIsCiAiZXhwIjogIjE0NzA3MzAxODIiLAogInVpZCI6ICIxMjM0NWFiY2RlIiwKfQ.9q2eq8sa374ao2uq9607r6qu6
四、JWT認證的實現
常規的token保存在sessionStorage或者localStorage中,每次請求時將token加在http請求的Header中,
下面是典型的token認證方式:
1.客戶端登錄時通過賬號和密碼到服務端進行認證,認證通過后,服務端通過持有的密鑰生成Token,Token中一般包含失效時長和用戶唯一標識,如用戶ID,服務端返回Token給客戶端;
2.客戶端保存服務端返回的Token;
3.客戶端進行業務請求時在Head的Authorization字段里面放置Token,如:
Authorization: Bearer Token
4.服務端對請求的Token進行校驗,如果Token不是存放在Cookie中,需要解決用戶主動注銷,但設置的過期時間並未過期問題。
用戶注銷時可以把還在失效內的Token儲存在Redis等緩存中,驗證時查找Token是否存在,如果Token在Redis中存在,則說明用戶已注銷;如果Token不存在,則校驗通過。
5.服務端可以通過從Token取得的用戶唯一標識進行相關權限的校驗,並把此用戶標識賦予到請求參數中,業務可通過此用戶標識進行業務處理;
還有一種方式是把token保存在Cookie中,這時就不需要在服務端保存token的值,用戶注銷時直接清除Cookie就可以,
這種方式不需要在服務端儲存token的值,認證過程如下:
五、JWT標准的安全性
(1)如何訪問CSRF攻擊
CSRF (Cross Site Request Forgery),指在一個瀏覽器中打開了兩個標簽頁,其中一個頁面通過竊取另一個頁面的 cookie 來發送偽造的請求,因為 cookie 是隨着請求自動發送到服務端的。
(2)如何保證token的安全性
客戶端不需要持有密鑰,由服務端通過密鑰生成Token;
在JWT中,不應該在Payload里面加入任何敏感的數據,如用戶密碼等信息,因為payload並沒有做加密,只是一個Base64的編碼,
攻擊者拿到token以后就可以得到用戶敏感信息;
參考資料:
JSON Web Token - 在Web應用間安全地傳遞信息