目前全文是翻譯的,講的主要是 cookie 驗證和 token 驗證的區(qū)別,cookie 驗證準確的說是利用 cookie 來作為媒介,存儲 session ID 進行驗證,token 其實也可以借助 cookie 來存儲,不過下文中提到的 cookie 驗證主要是指 session ID 存儲到 cookie 中進行的驗證。token 驗證主要是指 token 存儲在 Authorization Header 中進行的驗證。
這個翻譯并不是完全按照原本逐字意譯,有的是我看了原本以后根據(jù)自己的理解寫了出來,同時省略了一些我認為不重要的內(nèi)容,加了一點我自己的筆記和疑問的地方。
原文地址:https://auth0.com/blog/cookies-vs-tokens-definitive-guide/
下面是譯文:
Cookie
cookie 驗證是用于長時間用戶驗證,cookie 驗證是有狀態(tài)的,意味著驗證記錄或者會話需要一直在服務(wù)端和客戶端保持。服務(wù)器需要保持對數(shù)據(jù)庫活動會話的追蹤,當在前端創(chuàng)建了一個 cookie,cookie 中包含了一個 session 標識符。傳統(tǒng) cookie 會話的驗證流程:
用戶登錄,輸入賬號密碼
服務(wù)器驗證用戶賬號密碼正確,創(chuàng)建一個 session 存儲在數(shù)據(jù)庫(或者 redis)
將 session ID 放進 cookie 中,被存儲在用戶瀏覽器中。
再次發(fā)起請求,服務(wù)器直接通過 session ID 對用戶進行驗證
一旦用戶登出,則 session 在客戶端和服務(wù)器端都被銷毀
Token
token 驗證是無狀態(tài)的,服務(wù)器不記錄哪些用戶登錄了或者哪些 JWT 被發(fā)布了,而是每個請求都帶上了服務(wù)器需要驗證的 token,token 放在了 Authorization header 中,形式是 Bearer { JWT },但是也可以在 post body 里發(fā)送,甚至作為 query parameter。
驗證流程:
用戶輸入登錄信息
服務(wù)器判斷登錄信息正確,返回一個 token
token 存儲在客戶端,大多數(shù)通常在 local storage,但是也可以存儲在 session storage 或者 cookie 中。
接著發(fā)起請求的時候?qū)?token 放進 Authorization header,或者同樣可以通過上面的方式。
服務(wù)器端解碼 JWT 然后驗證 token,如果 token 有效,則處理該請求。
一旦用戶登出,token 在客戶端被銷毀,不需要經(jīng)過服務(wù)器端。
Token 驗證的優(yōu)勢
無狀態(tài),可擴展和解耦
使用 token 而不是 cookie 的最大優(yōu)點應(yīng)該就是無狀態(tài),后端不需要保持對 token 的記錄,每個 token 都是獨立的,包含了檢查其有效性的所有數(shù)據(jù),并通過申明傳達了用戶信息。
服務(wù)器端的工作只需要在登錄成功后,生成(或者 sign,簽署) token,或者驗證傳入的 token 是否有效。有時候甚至不需要生成 token,第三方服務(wù)比如 Auth0 可以處理 token 的簽發(fā),服務(wù)器只需要驗證 token 的有效性就可以。跨域和 CORS
cookie 能很好的處理單域和子域,但是遇到跨域的問題就會變得難以處理。而使用 token 的 CORS 可以很好的處理跨域的問題。由于每次發(fā)送請求到后端,都需要檢查 JWT,只要它們被驗證通過就可以處理請求。在 JWT 中存儲數(shù)據(jù)
當使用 cookie 進行驗證時,你是將 session id 存儲到 cookie 里,JWT 允許你存儲任何類型的元數(shù)據(jù),只要是合法的 JSON。你可以在里面添加任何數(shù)據(jù),可以只有用戶 ID 和到期日,也可以添加其它的比如郵件地址,域名等等。
比如:加入你有一個 API 是 /api/orders ,用于取回最新的訂單,但是只有 admin 角色的用戶可以獲取到這些數(shù)據(jù)。在基于 cookie 的驗證中,一旦請求被創(chuàng)建,就需要先去訪問數(shù)據(jù)庫去驗證 session 是否正確(現(xiàn)在應(yīng)該都是存儲到 redis 里了,不會存數(shù)據(jù)庫里了),另外還要去獲取數(shù)據(jù)庫里的用戶權(quán)限去校驗用戶是否擁有 admin 的權(quán)限(這個應(yīng)該是根據(jù)用戶 role_id 查看權(quán)限是否是 admin),最后才是調(diào)用訂單信息。而使用 JWT 的話,可以將用戶角色放進 JWT 內(nèi),所以只要驗證通過了,就可以直接調(diào)用訂單信息。移動平臺
現(xiàn)代的 API 不僅僅和瀏覽器交互,正確編寫一個 API 可以同時支持瀏覽器,還有原生移動平臺,比如 IOS 或者 Android。原生移動平臺并不一定和 cookie 能良好的兼容,在使用中會存在一些限制和需要注意的地方。另一方面,token 更容易在 IOS 和 Android 上實現(xiàn),Token 也更容易實現(xiàn)物聯(lián)網(wǎng)應(yīng)用程序和服務(wù),沒有 Cookie 存儲的概念。
共同的問題
這里介紹一些常見的問題,或者在令牌 token 的情況下出現(xiàn)時經(jīng)常出現(xiàn)。這里的關(guān)鍵重點將是安全性,但我們將介紹有關(guān) token 大小,存儲和加密的用例。
- JWT 大小
token 最大的缺點就是它的大小,最小的它都比 cookie 要大,如果 token 中包含很多聲明,那問題就會變得比較嚴重,畢竟向服務(wù)器發(fā)送的每個請求都要有這個 token。(意思應(yīng)該是太大了會導(dǎo)致請求緩慢) - 哪里存儲 token
通常 JWT 被存儲在瀏覽器的 local storage 中并且能夠很好的運用,但是這樣存儲也會有問題,不像 cookie,local storage 被沙盒化到特定域,其區(qū)域不能被任何其他域訪問,包括子域。
你可以存儲 token 在 cookie 中,但是 cookie 最大的大小也只有 4kb,所以如果你有許多聲明的時候可能會存儲不夠,session storage 就更不用說了,會話斷開就被清除掉了。
(個人記錄:由于JWT前兩個字符串采用base64進行編碼,所以內(nèi)容越多,編碼字符串長度越長) - XSS 和 XSRF 防護
保護用戶信息安全和服務(wù)器數(shù)據(jù)始終是首要任務(wù)。最常見的網(wǎng)絡(luò)攻擊就是 XSS 和 CSRF。
如果用戶輸入或提交的信息沒有得到過濾的話,如果一些攻擊代碼可以在你訪問的域名下執(zhí)行,那你的 JWT token 會被泄露。與 CSRF 相比,XSS 會更容易處理(因為 XSS 很容易理解)。許多框架包括 Angular 等等,都會自動過濾掉 input 輸入的某些內(nèi)容且防止任意代碼執(zhí)行。如果框架本身不自帶這種過濾機制,可以采用一些插件比如 caja。這種過濾 input 輸入框是大部分框架和語言解決 XSS 問題的一個方式。
如果你通過 local storage 使用 JWT,那么可以避免 CSRF,但是另一方面,如果你用 cookie 來存儲 JWT,那就需要防護 CSRF,CSRF 并不像 XSS 攻擊那么好理解。解釋 CSRF 攻擊可能會非常耗時。為了避免過度簡化,防止 CSRF 攻擊,你的服務(wù)器會和客戶端建立會話后,會生成一個唯一的 token(這不是 JWT)。然后隨時將數(shù)據(jù)提交到你的服務(wù)器,隱藏的 input 將會包含這個 token,服務(wù)器將會檢查這個 token 以確保 token 匹配。不夠我們是建議將 JWT 存儲在 local storage 中,你也不太需要擔心 CSRF 攻擊的問題。
其中一個最好的保護用戶和服務(wù)器的方法就是有一個給 token 一個短期過期時間,這樣即使 token 被其他人獲取,但是也會很快不能再用。此外,你可以維護一個受攻擊的 token 黑名單,不允許這些黑名單的 token 訪問系統(tǒng)。最后,還可以統(tǒng)一更改 token 算法,讓所有 token 都失效,并讓用戶重新登錄系統(tǒng),不過這種方法一般不推薦,只有在被攻擊比較嚴重的情況下使用。 - token Are Signed,Not Encrypted
一個 JWT 是由三部分構(gòu)成:header,payload,signature。
header: 算法和 token 類型,如
{ "alg": "HS256", "typ": "JWT"}
payload: 數(shù)據(jù),如
{ "sub": "12", "name": "Ado Kukic", "admin": true}
驗證簽名
HMACSHA256(
base64UrlEncode(header)+"." +
base64UrlEncode(payload),
secret
)
JWT 的格式為 header.payload.signature
,如果我們需要通過 HMACSHA256 算法簽發(fā)一個 JWT ,如果 secret 是 'shhhh' 以及 payload 是
{ "sub": "1234567890", "name": "Ado Kukic", "admin": true}
則 JWT 為
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkFkbyBLdWtpYyIsImFkbWluIjp0cnVlLCJpYXQiOjE0NjQyOTc4ODV9.Y47kJvnHzU9qeJIN48_bVna6O0EDFiMiQ9LpNVDFymM
(疑問:secret 在這里是干嘛的)
這里有一個非常重要的提醒,token 是通過 HMACSHA256 算法生成,header 和 payload 是 Base64URL 編碼,這不是加密。如果你去訪問 jwt.io 網(wǎng)站,粘貼 token 以及選擇 HMACSHA256 算法,可以解析出 token 讀取它詳細的內(nèi)容。因此里面不能攜帶一些敏感數(shù)據(jù),比如密碼,絕對不能存儲在 payload 中。
如果你必須存儲敏感信息在 payload 中,或者使 JWT 被隱藏,你可以使用 JWE(JSON Web Encryption)。JWE 允許你去加密 JWT 的內(nèi)容,讓其除了服務(wù)器以外的任何人都不可讀。JOSE 為 JWE 提供了一個很好的框架,并為許多流行框架(包括 NodeJS 和 Java)提供了 SDK。
token 驗證在 Auth0 上的實踐
未完待續(xù)...