1. 程式人生 > >關於 Token,你應該知道的十件事

關於 Token,你應該知道的十件事

敏感信息 you load 冒充 tro hex 服務器 xhr cors

轉自:http://ju.outofmemory.cn/entry/134189

原文是一篇很好的講述 Token 在 Web 應用中使用的文章,而這是我和 Special 合作翻譯的譯文。

1. Token 應該被保存起來(放到 local / session stograge 或者 cookies)

在單頁應用程序中,有些用戶刷新瀏覽器後會帶來一些跟 token 相關的問題。而解決方法很簡單:你應該把 token 保存到起來:放到 session storage, local storage 或者是客戶端的 cookie 裏。而瀏覽器不支持 session storage 時都應該轉存到 cookies 裏。

如果你想“我把 token 保存到 cookie ,不就跟以前沒有任何分別?”。可是在這種情況下你只是把 cookie 當作一個儲存機制,而不是一種驗證機制。(比如說,這個 cookie 不會被 Web 框架用於用戶驗證,所以沒有 XSRF 攻擊的危險)。

2. Tokens 除了像 cookie 一樣有有效期,而且你可以有更多的操作方法

Tokens 應該有一個有效期(在 JSON Web Tokens 中是作為 exp 屬性),否則其他人只要登錄過一次就可以永遠地通過 API 的驗證。Cookies 基於同樣的理由也有一個有效期。

在 Cookies 的使用中,有不同的選項可以控制 cookie 的生命周期:

. cookies 可以在瀏覽器關閉後刪除(session cookies);
. 另外你可以實現服務器端的檢查(通常由你使用的 Web 框架完成),還有也可以實現絕對有效期或彈性有效期(sliding window expiration);
. Cookies 可以帶有有效期地保存起來(瀏覽器關閉後也不刪除)。

而在 tokens 的使用中,一旦 token 過期,只需要重新獲取一個。你可以使用一個接口去刷新 token:

. 讓舊的 token 失效;
. 檢查這個用戶是不是還存在,權限是否被取消或者任何對你的程序來說是有必要的;
. 得到一個更新了有效期的 token

你甚至可以把 token 原來的發布時間也保存起來,並且強制在兩星期後重新登錄什麽的。

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 的生成管理器去作檢查。

3. Local / session storage 不會跨域工作,請使用一個標記 cookie

如果你設置一個 cookie 的域名為 .yourdomain.com 它將可以被 youdomain.comapp.yourdomain.com 獲取,這樣用戶登錄並且轉到 app.yourdomain.com 後也能很容易地從主域名找回這個 cookie(假如你的是電商網站)。

而另一方面,保存在 local / session storage 的 tokens,就不能從不同的域名中讀取(甚至是子域名也不行)。那你能怎麽做?

一個可能的選擇是,當用戶通過 app.yourdomain.com 上面的驗證時你生成一個 token 並且作為一個 cookie 保存到 .yourdomain.com

$.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 作為驗證方法,只是作為一個存儲機制去支持存儲信息在不同的域名中。

4. 每個 CORS(跨域資源共享)請求都會帶上預請求(Preflight request)

有些人指出 Authorization header 不是一個simple header,因此對於一個特定的 URLs 的所有請求都會帶上一個預請求。

OPTIONS https://api.foo.com/bar
GET https://api.foo.com/bar
   Authorization: Bearer ....

OPTIONS https://api.foo.com/bar2
GET 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 因為某些原因好像會有問題)。

5. 當你需要流傳送某些東西,請用 token 去獲取一個已簽名的請求。

當使用 cookies 時,你可以很容易開始一個文件的下載或流傳送內容。然而,在 tokens 的使用中,請求是通過 XHR 完成的,你不能依賴於它。而解決方法應該是像 AWS 那樣通過生成一個簽名了的請求,例如,Hawk Bewits 是一個很好的框架去啟用它:

Request:

POST /download-file/123
Authorization: Bearer...

Response:

ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja

這個 ticket 是無狀態並且是基於 URL 的:host + path + query + headers + timestamp + HMAC,並且有一個有效期。所以它可以用於像只能在5分鐘內去下載一個文件。

你然後可以轉到 /download-file/123? ticket=lahdoiasdhoiwdowijaksjdoaisdjoasidja 中去。服務器就會檢查這個 ticket 是不是有效然後像正常一樣開始下一步的服務。

6. 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 。

7. 註意 token 的大小

Token 機制在每次請求 API 的時候,都需要帶上一個 Authorization 的 Http Header 。

# Token
GET /foo
Authorization: Bearer ...2kb token...
# Cookie
GET /foo
connect.sid: ...20 bytes cookie...

Token 的大小其實由你儲存在 token 中的信息量所決定,例如可能有 nicknameopenid 等開發者另外加上的信息。

但是 session cookies 機制只需要一個字串作為用戶標識即可(例如 PHP 的 PHPSESSIONID),其中關於用戶的信息都會直接儲存到服務端的數據庫中,當用戶請求時才從數據庫中撈出來用。

當然 Token 機制也可以仿照 session cookies 機制這麽做了,也是個有效控制 token 大小的方法。

Token 中只保留關鍵的幾條身份標識信息,其余都放到數據庫裏面了,權限控制的時候再撈出。這樣做的好處是,開發者可以完全掌控 token,因為關鍵信息都已經是你代碼和數據庫中的一部分了,想怎麽弄都可以了。

舉個例子:

GET /foo
Authorization: Bearer ……500 bytes token….
Then on the server:
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。

8. 有需要的話,要加密並且簽名 token

雖然 TLS/SSL 機制可以隔絕大多數中間人攻擊,但是如果 token 中帶有了用戶的敏感信息,開發者也應該要加密這些信息。

使用 JWT(文中第 9 點) 可以加密 token,但是由於目前大多數 Web 框架還未支持 JWT,所以可以使用 AES-CBC 算法加密 token。

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 點,直接將敏感信息丟數據庫中。

9. 將 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 上。

10. Tokens 不是萬能的解決方法,得根據你的需求自行采用

這些年來,我們幫助過不少大公司實現了他們的以 Token 為基礎的驗證授權架構。曾經有一家 10k + 員工,有著大量數據的公司,他們想實現一個中央權限管理系統,其中有一個需要是某個員工只能讀取某個國家某個醫院某個床位的idname字段數據,想想這樣的細粒度的權限管理是多麽難實現,無論是技術上還是行政上。

當然采用 tokens 與否,得看大家的具體需求,但是,要忠告大家的是,不要什麽內容都寫到 tokens 了,加之前想想有沒有這個必要。

關於 Token,你應該知道的十件事