轉自 http://alvinzhu.me/blog/2014/08/26/10-things-you-should-know-about-tokens/
原文是一篇很好的講述 Token 在 Web 應用中使用的文章,而這是我和 Special 合作翻譯的譯文。
- Token 應該被保存起來(放到 local / session stograge 或者 cookies)
在單頁應用程序中,有些用戶刷新瀏覽器后會帶來一些跟 token 相關的問題。而解決方法很簡單:你應該把 token 保存到起來:放到 session storage, local storage 或者是客戶端的 cookie 里。而瀏覽器不支持 session storage 時都應該轉存到 cookies 里。
如果你想“我把 token 保存到 cookie ,不就跟以前沒有任何分別?”??墒窃谶@種情況下你只是把 cookie 當作一個儲存機制,而不是一種驗證機制。(比如說,這個 cookie 不會被 Web 框架用于用戶驗證,所以沒有 XSRF 攻擊的危險)。 - Tokens 除了像 cookie 一樣有有效期,而且你可以有更多的操作方法
Tokens 應該有一個有效期(在 JSON Web Tokens 中是作為 exp
屬性),否則其他人只要登錄過一次就可以永遠地通過 API 的驗證。Cookies 基于同樣的理由也有一個有效期。
在 Cookies 的使用中,有不同的選項可以控制 cookie 的生命周期: - cookies 可以在瀏覽器關閉后刪除(session cookies);2. 另外你可以實現服務器端的檢查(通常由你使用的 Web 框架完成),還有也可以實現絕對有效期或彈性有效期(sliding window expiration);3. Cookies 可以帶有有效期地保存起來(瀏覽器關閉后也不刪除)。
而在 tokens 的使用中,一旦 token 過期,只需要重新獲取一個。你可以使用一個接口去刷新 token:
- 讓舊的 token 失效;2. 檢查這個用戶是不是還存在,權限是否被取消或者任何對你的程序來說是有必要的;3. 得到一個更新了有效期的 token。
你甚至可以把 token 原來的發布時間也保存起來,并且強制在兩星期后重新登錄什么的。
12345678910111213141516
app.post('/refresh_token', function (req, res) { // verify the existing token var profile = jwt.verify(req.body.token, secret); // if more than 14 days old, force login if (profile.original_iat - new Date() > 14) { // iat == issued at return res.send(401); // re-logging } // check if the user still exists or if authorization hasn't been revoked if (!valid) return res.send(401); // re-logging // issue a new token var refreshed_token = jwt.sign(profile, secret, { expiresInMinutes: 60*5 }); res.json({ token: refreshed_token });});
如果你需要撤回 tokens(當 token 的生存期比較長的時候這很有必要)那么你需要一個 token 的生成管理器去作檢查。
- Local / session storage 不會跨域工作,請使用一個標記 cookie
如果你設置一個 cookie 的域名為 .yourdomain.com
它將可以被 youdomain.com
和 app.yourdomain.com
獲取,這樣用戶登錄并且轉到app.yourdomain.com
后也能很容易地從主域名找回這個 cookie(假如你的是電商網站)。
而另一方面,保存在 local / session storage 的 tokens,就不能從不同的域名中讀?。ㄉ踔潦亲佑蛎膊恍校D悄隳茉趺醋??
一個可能的選擇是,當用戶通過 app.yourdomain.com
上面的驗證時你生成一個 token 并且作為一個 cookie 保存到 .yourdomain.com
1234567
$.post('/authenticate, function() { // store token on local/session storage or cookie .... // create a cookie signaling that user is logged in $.cookie('loggedin', profile.name, '.yourdomain.com');});
然后,在 youromdain.com
中你可以檢查這個 cookie 是不是已經存在了,并且如果存在的話就轉到 app.youromdain.com
去。從這以后,這個 token 將會對程序的子域名以及之后通常的流程都有效(直到這個 token 超過有效期)。
不過這將會導致 cookie 存在但 token 被刪除了或其他意外情況的發生。在這種情況下,用戶將不得不重新登錄。但重要的是,像我們之前說的,我們不會這個用 cookie 作為驗證方法,只是作為一個存儲機制去支持存儲信息在不同的域名中。
- 每個 CORS(跨域資源共享)請求都會帶上預請求(Preflight request)
有些人指出 Authorization header 不是一個simple header,因此對于一個特定的 URLs 的所有請求都會帶上一個預請求。
12345678910
OPTIONS https://api.foo.com/barGET https://api.foo.com/bar Authorization: Bearer ....OPTIONS https://api.foo.com/bar2GET https://api.foo.com/bar2 Authorization: Bearer ....GET https://api.foo.com/bar Authorization: Bearer ....
但這只會發生在你發送 Content-Type: application/json
時。不過這說明已經出現在絕大多數的程序中了。
一個小小的警告,the OPTIONS
請求不會帶有 Authorization header 自身,所以你的網絡框架應該支持區別對待 OPTISON
和后來的請求。(微軟的 IIS 因為某些原因好像會有問題)。
- 當你需要流傳送某些東西,請用 token 去獲取一個已簽名的請求。
當使用 cookies 時,你可以很容易開始一個文件的下載或流傳送內容。然而,在 tokens 的使用中,請求是通過 XHR 完成的,你不能依賴于它。而解決方法應該是像 AWS 那樣通過生成一個簽名了的請求,例如,Hawk Bewits 是一個很好的框架去啟用它:
Request:
12
POST /download-file/123Authorization: Bearer...
Response:
1
ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja
這個 ticket 是無狀態并且是基于 URL 的:host + path + query + headers + timestamp + HMAC,并且有一個有效期。所以它可以用于像只能在5分鐘內去下載一個文件。
你然后可以轉到 /download-file/123? ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja
中去。服務器就會檢查這個 ticket 是不是有效然后像正常一樣開始下一步的服務。
-
XSS 比 XSRF 要更容易防范
XSS 攻擊的原理是,攻擊者插入一段可執行的 JavaScripts 腳本,該腳本會讀出用戶瀏覽器的 cookies 并將它傳輸給攻擊者,攻擊者得到用戶的 Cookies 后,即可冒充用戶。但是要防范 XSS 也很簡單,在寫入 cookies 時,將 HttpOnly
設置為 true
,客戶端 JavaScripts 就無法讀取該 cookies 的值,就可以有效防范 XSS 攻擊。因為 Tokens 也是儲存在本地的 session storage 或者是客戶端的 cookies 中,也是會受到 XSS 攻擊。所以在使用 tokens 的時候,必須要考慮過期機制,不然攻擊者就可以永久持有受害用戶帳號。
相比 XSS,XSRF 的危害性更大,因為大多數 Web 框架都已經內置了 XSS 防范機制(例如在 Ruby on Rails 中,用戶的輸入在輸出的時候都會做轉義
操作,攻擊者插入的腳本就無法執行),對于大部分開發者而言,甚至連 XSRF 都不知道是什么玩意,更別提防范了。XSRF 目前并不是每個 Web 框架都有防范機制,因此開發者更應該留意 XSRF 。 - 注意 token 的大小
Token 機制在每次請求 API 的時候,都需要帶上一個 Authorization
的 Http Header 。
123
TokenGET /fooAuthorization: Bearer ...2kb token...
123
CookieGET /fooconnect.sid: ...20 bytes cookie...
Token 的大小其實由你儲存在 token 中的信息量所決定,例如可能有 nickname
,openid
等開發者另外加上的信息。
但是 session cookies 機制只需要一個字串作為用戶標識即可(例如 PHP 的 PHPSESSIONID),其中關于用戶的信息都會直接儲存到服務端的數據庫中,當用戶請求時才從數據庫中撈出來用。
當然 Token 機制也可以仿照 session cookies 機制這么做了,也是個有效控制 token 大小的方法。
Token 中只保留關鍵的幾條身份標識信息,其余都放到數據庫里面了,權限控制的時候再撈出。這樣做的好處是,開發者可以完全掌控 token,因為關鍵信息都已經是你代碼和數據庫中的一部分了,想怎么弄都可以了。
舉個例子:
123
GET /fooAuthorization: Bearer ……500 bytes token….Then on the server:
12345678910
app.use('/api', // 首先檢查 token; expressJwt({secret: secret}), // 然后再從數據庫中撈出用戶信息。 function(req, res, next) { req.user.extra_data = get_from_db(); next(); });
另外值得一提的是,你也可以把東西都丟 Cookies 里面(而不是只丟個身份標識字串)。只要確保資料經過了嚴格的加密,攻擊者無法利用,現在有些 Web 框架已經有類似機制,例如 Nodejs 的這個插件 mozilla/node-client-sessions。
- 有需要的話,要加密并且簽名 token
雖然 TLS/SSL 機制可以隔絕大多數中間人攻擊,但是如果 token 中帶有了用戶的敏感信息,開發者也應該要加密這些信息。
使用 JWT(文中第 9 點) 可以加密 token,但是由于目前大多數 Web 框架還未支持 JWT,所以可以使用 AES-CBC 算法加密 token。
1234567891011121314151617181920
app.post('/authenticate', function (req, res) { // 校驗用戶; // 加密 token; var encrypted = { token: encryptAesSha256('shhhh', JSON.stringify(profile)) }; // 給加密后的 token 簽名; var token = jwt.sign(encrypted, secret, { expiresInMinutes: 60*5 }); res.json({ token: token });}function encryptAesSha256 (password, textToEncrypt) { var cipher = crypto.createCipher('aes-256-cbc', password); var crypted = cipher.update(textToEncrypt, 'utf8', 'hex'); crypted += cipher.final('hex'); return crypted;}// 上面就是 encrypt-then-MAC (加密后簽名)做法。
當然你也可以用文中的第 7 點,直接將敏感信息丟數據庫中。
- 將 JSON Web Tokens 應用到 OAuth 2
OAuth 2 是一個解決身份驗證的授權協議,并且廣泛地使用了 token 。
用戶通過 OAuth 2 協議授權第三方應用權限,然后服務器返回一個 access_token
給第三方應用,通常也帶有 scope
參數,第三方應用通過帶上access_token
請求服務器,可以在授權范圍(scope)內調用 API。
一般來說,類似這種 token 是不透明的,就是核心數據都儲存以 hash-table 結果儲存在服務器中,客戶端只持有一個令牌
(access_token),任何人都可以用這個令牌在授權范圍(scope)內調用服務器端的 API。
Signed tokens(例如 JWT))和這種形式的 token 最主要的區別是,JWT 是無狀態的,它不儲存在服務端 hash-table 中,服務端中不保留 JWT 請求的相關信息,JWT 會把授權信息和 API 調用返回都丟一起返回給客戶端。
JWT 通常以 Base64 + AES 方式編碼傳輸。OAuth 2 協議也支持 JWT,因為 OAuth 2 并未限制 access_token 數據格式,你可以將 JWT 應用在 OAuth 2 上。 - Tokens 不是萬能的解決方法,得根據你的需求自行采用
這些年來,我們幫助過不少大公司實現了他們的以 Token 為基礎的驗證授權架構。曾經有一家 10k + 員工,有著大量數據的公司,他們想實現一個中央權限管理系統,其中有一個需要是某個員工只能讀取某個國家某個醫院某個床位的id
和name
字段數據,想想這樣的細粒度的權限管理是多么難實現,無論是技術上還是行政上。
當然采用 tokens 與否,得看大家的具體需求,但是,要忠告大家的是,不要什么內容都寫到 tokens 了,加之前想想有沒有這個必要。