授權認證登錄之 Cookie、Session、Token、JWT 詳解

目錄

 

一、先了解幾個基礎概念

二、什麼是 Cookie

1. cookie 重要的屬性

2. 服務器端設置cookie示例(Node)

3. 客戶端對Cookie的存取

4. 每個域名下cookie個數限制

5. 封裝對Cookie的操作

三、什麼是 Session

四、Cookie 和 Session 的區別

五、什麼是 Token(令牌)

Acesss Token

Refresh Token

六、Token 和 Session 的區別

七、什麼是 JWT

1. JWT 的原理

2. JWT 的數據結構

八、Token 和 JWT 的區別

九、要注意的問題

使用 session 時需要考慮的問題

使用 JWT 時需要考慮的問題

使用加密算法時需要考慮的問題

只要關閉瀏覽器 ,session 真的就消失了?

參考文章


一、先了解幾個基礎概念

什麼是認證(Authentication)

通俗地講就是驗證當前用戶的身份

互聯網中的認證:

  • 用戶名密碼登錄
  • 郵箱發送登錄鏈接
  • 手機號接收驗證碼
  • 只要你能收到郵箱/驗證碼,就默認你是賬號的主人

什麼是授權(Authorization)

用戶授予第三方應用訪問該用戶某些資源的權限。

實現授權的方式有:cookie、session、token、OAuth。

什麼是憑證(Credentials)

實現認證和授權的前提是需要一種媒介(證書)來標記訪問者的身份。

在互聯網應用中,一般網站(如掘金)會有兩種模式,遊客模式和登錄模式。遊客模式下,可以正常瀏覽網站上面的文章,一旦想要點贊/收藏/分享文章,就需要登錄或者註冊賬號。當用戶登錄成功後,服務器會給該用戶使用的瀏覽器頒發一個令牌(token),這個令牌用來表明你的身份,每次瀏覽器發送請求時會帶上這個令牌,就可以使用遊客模式下無法使用的功能。

二、什麼是 Cookie

  • Cookie 最開始被設計出來是爲了彌補HTTP狀態管理上的不足HTTP 協議是一個無狀態協議,客戶端向服務器發請求,服務器返回響應,故事就這樣結束了,但是下次發請求如何讓服務端知道客戶端是誰呢?這種背景下,就產生了 Cookie。
  • cookie 存儲在客戶端: cookie 是服務器發送到用戶瀏覽器並保存在本地的一小塊數據,它會在瀏覽器下次向同一服務器再發起請求時被攜帶併發送到服務器上。
  • cookie 是不可跨域的: 每個 cookie 都會綁定單一的域名(綁定域名下的子域都是有效的),無法在別的域名下獲取使用,同域名不同端口也是允許共享使用的

服務器向客戶端發送Cookie是通過HTTP響應報文實現的,在Set-Cookie中設置需要向客戶端發送的cookie,cookie格式如下:

Set-Cookie: "name=value;domain=.domain.com;path=/;expires=Sat, 11 Jun 2016 11:29:42 GMT;HttpOnly;secure"

1. cookie 重要的屬性

屬性 說明
name=value 鍵值對,設置 Cookie 的名稱及相對應的值,都必須是字符串類型(name 不區分大小寫)
- 如果值爲 Unicode 字符,需要爲字符編碼。
- 如果值爲二進制數據,則需要使用 BASE64 編碼。
domain 指定 cookie 所屬域名,默認是當前域名
path 指定 cookie 在哪個路徑(路由)下生效,默認是 '/'
如果設置爲 /abc,則只有 /abc 下的路由可以訪問到該 cookie,如:/abc/read
expires

過期時間(GMT時間格式),在設置的某個時間點後該 cookie 就會失效。

如果客戶端和服務器時間不一致,使用expires就會存在偏差。
一般瀏覽器的 cookie 都是默認儲存的,當關閉瀏覽器結束這個會話的時候,這個 cookie 也就會被刪除

max-age

cookie 失效的時間,單位秒。如果爲正數,則該 cookie 在 maxAge 秒後失效。如果爲負數,該 cookie 爲臨時 cookie ,關閉瀏覽器即失效,瀏覽器也不會以任何形式保存該 cookie 。如果爲 0,表示刪除該 cookie 。默認爲 -1。
- 優先級高於 expires 

HttpOnly 如果給某個 cookie 設置了 httpOnly 屬性,則無法通過 JS 腳本 讀寫該 cookie 的信息,但還是能通過 Application 中手動修改 cookie,所以只是在一定程度上可以防止 CSRF 攻擊,不是絕對的安全
secure 該 cookie 是否僅被使用安全協議傳輸。安全協議有 HTTPS,SSL等,在網絡上傳輸數據之前先將數據加密。默認爲false。
當 secure 值爲 true 時,cookie 在 HTTP 中是無效的。

2. 服務器端設置cookie示例(Node)

var http = require('http');
var fs = require('fs');
 
http.createServer(function(req, res) {
    res.setHeader('status', '200 OK');
    res.setHeader('Set-Cookie', 'isVisit=true;domain=.yourdomain.com;path=/;max-age=1000');
    res.write('Hello World');
    res.end();
}).listen(8888);
 
console.log('running localhost:8888')

3. 客戶端對Cookie的存取

讀取cookie:

document.cookie 返回當前頁面可用的(根據cookie的域、路徑、失效時間和安全設置)所有cookie(字符串格式),例如:

document.cookie;
// "name1=value1; name2=value2"

設置cookie:

document.cookie = "name=Jim";

"name=Jim"會被添加到現有的cookie集合中。

4. 每個域名下cookie個數限制

  • Chrome和Safari沒有做硬性限制
  • Firefox最多50個cookie
  • IE7和之後的版本最後可以有50個cookie
  • IE6或更低版本最多20個cookie

瀏覽器一般只允許存放 300 個Cookie。

5. 封裝對Cookie的操作

由於cookie的讀寫非常不方便,我們可以自己封裝一些函數來處理cookie,主要是針對cookie的添加、刪除、修改。

class cookieUtils {
  get(name) {
    var arr,
      reg = new RegExp("(^| )" + name + "=([^;]*)(;|$)");
    if ((arr = document.cookie.match(reg))) return decodeURI(arr[2]);
    else return null;
  }
 
  set(name, value, expiresTime) {
    var date = new Date();
    // expiresTime 以天計算
    date.setTime(date.getTime() + expiresTime * 24 * 3600 * 1000);
    document.cookie =
      name +
      "=" +
      value +
      (expiresTime == null ? "" : ";expires=" + date.toGMTString());
  }
 
  delete(name) {
    var date = new Date();
    date.setTime(date.getTime() - 10000);
    document.cookie = name + "=-1;expires=" + date.toGMTString();
  }
}
 
export default new cookieUtils();

在Chrome控制檯Application 的 Cookies 裏可以對 cookie 進行讀寫操作。

移動端對 cookie 的支持不是很好,而 session 需要基於 cookie 實現,所以移動端常用的是 token。

三、什麼是 Session

  • session 是另一種記錄服務器和客戶端會話狀態的機制
  • session 是基於 cookie 實現的,session 存儲在服務器端,sessionId 會被存儲到客戶端的cookie 中

session 認證流程:

  • 用戶第一次請求服務器的時候,服務器根據用戶提交的相關信息,創建對應的 Session
  • 請求返回時將此 Session 的唯一標識 SessionID 返回給瀏覽器
  • 瀏覽器接收到服務器返回的 SessionID 後,會將此信息存入到 Cookie 中,同時 Cookie 記錄此 SessionID 屬於哪個域名
  • 當用戶第二次訪問服務器的時候,請求會自動把此域名下的 Cookie 信息也發送給服務端,服務端會從 Cookie 中獲取 SessionID,再根據 SessionID 查找對應的 Session 信息,如果沒有找到說明用戶沒有登錄或者登錄失效,如果找到 Session 證明用戶已經登錄可執行後面操作。

根據以上流程可知,SessionID 是連接 Cookie 和 Session 的一道橋樑,大部分系統也是根據此原理來驗證用戶登錄狀態。

四、Cookie 和 Session 的區別

  • 安全性: Session 比 Cookie 安全,Session 是存儲在服務器端的,Cookie 是存儲在客戶端的。
  • 存取值的類型不同:Cookie 只支持存字符串數據,Session 可以存任意數據類型。
  • 有效期不同: Cookie 可設置爲長時間保持,比如我們經常使用的默認登錄功能,Session 一般失效時間較短,客戶端關閉(默認情況下)或者 Session 超時都會失效。
  • 存儲大小不同: 單個 Cookie 保存的數據不能超過 4K,Session 可存儲數據遠高於 Cookie,但是當訪問量過多,會佔用過多的服務器資源。

五、什麼是 Token(令牌)

Acesss Token

  • 訪問資源接口(API)時所需要的資源憑證
  • 簡單 token 的組成: uid(用戶唯一的身份標識)、time(當前時間的時間戳)、sign(簽名,token 的前幾位以哈希算法壓縮成的一定長度的十六進制字符串)

服務器對 Token 的存儲方式:

  1. 存到數據庫中,每次客戶端請求的時候取出來驗證(服務端有狀態)
  2. 存到 redis 中,設置過期時間,每次客戶端請求的時候取出來驗證(服務端有狀態)
  3. 不存,每次客戶端請求的時候根據之前的生成方法再生成一次來驗證(JWT,服務端無狀態)
  • 特點:
    • 服務端無狀態化、可擴展性好
    • 支持移動端設備
    • 安全
    • 支持跨程序調用
  • token 的身份驗證流程:

  1. 客戶端使用用戶名跟密碼請求登錄
  2. 服務端收到請求,去驗證用戶名與密碼
  3. 驗證成功後,服務端會簽發一個 token 並把這個 token 發送給客戶端
  4. 客戶端收到 token 以後,會把它存儲起來,比如放在 cookie 裏或者 localStorage 裏
  5. 客戶端每次向服務端請求資源的時候需要帶着服務端簽發的 token
  6. 服務端收到請求,然後去驗證客戶端請求裏面帶着的 token ,如果驗證成功,就向客戶端返回請求的數據
  • 每一次請求都需要攜帶 token,需要把 token 放到 HTTP 的 Header 裏
  • token 完全由應用管理,所以它可以避開同源策略

注意:登錄時 token 不宜保存在 localStorage,被 XSS 攻擊時容易泄露。所以比較好的方式是把 token 寫在 cookie 裏。爲了保證 xss 攻擊時 cookie 不被獲取,還要設置 cookie 的 http-only。這樣,我們就能確保 js 讀取不到 cookie 的信息了。再加上 https,能讓我們的請求更安全一些。

Refresh Token

  • 另外一種 token——refresh token
  • refresh token 是專用於刷新 access token 的 token。如果沒有 refresh token,也可以刷新 access token,但每次刷新都要用戶輸入登錄用戶名與密碼,會很麻煩。有了 refresh token,可以減少這個麻煩,客戶端直接用 refresh token 去更新 access token,無需用戶進行額外的操作。

  • Access Token 的有效期比較短,當 Acesss Token 由於過期而失效時,使用 Refresh Token 就可以獲取到新的 Token,如果 Refresh Token 也失效了,用戶就只能重新登錄了。
  • Refresh Token 及過期時間是存儲在服務器的數據庫中,只有在申請新的 Acesss Token 時纔會驗證,不會對業務接口響應時間造成影響,也不需要向 Session 一樣一直保持在內存中以應對大量的請求。

六、Token 和 Session 的區別

  • Session 是一種記錄服務器和客戶端會話狀態的機制,使服務端有狀態化,可以記錄會話信息。而 Token 是令牌,訪問資源接口(API)時所需要的資源憑證。Token 使服務端無狀態化,不會存儲會話信息。
  • Session 和 Token 並不矛盾,作爲身份認證 Token 安全性比 Session 好,因爲每一個請求都有簽名還能防止監聽以及重複攻擊,而 Session 就必須依賴鏈路層來保障通訊安全了。如果你需要實現有狀態的會話,仍然可以增加 Session 來在服務器端保存一些狀態。
  • 如果你的用戶數據可能需要和第三方共享,或者允許第三方調用 API 接口,用 Token 。如果永遠只是自己的網站,自己的 App,用什麼就無所謂了。

七、什麼是 JWT

  • JSON Web Token(簡稱 JWT)是目前最流行的跨域認證解決方案。
  • 是一種認證授權機制
  • JWT 是爲了在網絡應用環境間傳遞聲明而執行的一種基於 JSON 的開放標準。JWT 的聲明一般被用來在身份提供者和服務提供者間傳遞被認證的用戶身份信息,以便於從資源服務器獲取資源。比如用在用戶登錄上。
  • 可以使用 HMAC 算法或者是 RSA 的公/私祕鑰對 JWT 進行簽名。因爲數字簽名的存在,這些傳遞的信息是可信的。

1. JWT 的原理

JWT 的原理是,服務器認證以後,生成一個 JSON 對象,返回給用戶,就像下面這樣。

{
  "姓名": "張三",
  "角色": "管理員",
  "到期時間": "2018年7月1日0點0分"
}

以後,用戶與服務端通信的時候,都要發回這個 JSON 對象。服務器完全只靠這個對象認定用戶身份。爲了防止用戶篡改數據,服務器在生成這個對象的時候,會加上簽名。

JWT 認證流程:

  • 用戶輸入用戶名/密碼登錄,服務端認證成功後,會返回給客戶端一個 JWT
  • 客戶端將 token 保存到本地(通常使用 localstorage,也可以使用 cookie)
  • 當用戶希望訪問一個受保護的路由或者資源的時候,需要請求頭的 Authorization 字段中使用Bearer 模式添加 JWT,其內容看起來是下面這樣
Authorization: Bearer <token>
  • 服務端的保護路由將會檢查請求頭 Authorization 中的 JWT 信息,如果合法,則允許用戶的行爲
  • 因爲 JWT 是自包含的(內部包含了一些會話信息),因此減少了需要查詢數據庫的需要
  • 因爲 JWT 並不使用 Cookie 的,所以你可以使用任何域名提供你的 API 服務而不需要擔心跨域問題
  • 因爲用戶的狀態不再存儲在服務端的內存中,所以這是一種無狀態的認證機制

2. JWT 的數據結構

實際的 JWT 大概就像下面這樣:

它是一個很長的字符串,中間用點(.)分隔成三個部分。

JWT 的三個部分依次如下:

  • Header(頭部)
  • Payload(負載)
  • Signature(簽名)

JWT詳細數據結構

生成 JWT

八、Token 和 JWT 的區別

相同:

  • 都是訪問資源的令牌
  • 都可以記錄用戶的信息
  • 都是使服務端無狀態化
  • 都是隻有驗證成功後,客戶端才能訪問服務端上受保護的資源

區別:

  • Token:服務端驗證客戶端發送過來的 Token 時,還需要查詢數據庫獲取用戶信息,然後驗證 Token 是否有效。
  • JWT: 將 Token 和 Payload 加密後存儲於客戶端,服務端只需要使用密鑰解密進行校驗(校驗也是 JWT 自己實現的)即可,不需要查詢或者減少查詢數據庫,因爲 JWT 自包含了用戶信息和加密的數據。

九、要注意的問題

使用 session 時需要考慮的問題

  • 將 session 存儲在服務器裏面,當用戶同時在線量比較多時,這些 session 會佔據較多的內存,需要在服務端定期的去清理過期的 session
  • 當網站採用集羣部署的時候,會遇到多臺 web 服務器之間如何做 session 共享的問題。因爲 session 是由單個服務器創建的,但是處理用戶請求的服務器不一定是那個創建 session 的服務器,那麼該服務器就無法拿到之前已經放入到 session 中的登錄憑證之類的信息了。
  • 當多個應用要共享 session 時,除了以上問題,還會遇到跨域問題,因爲不同的應用可能部署的主機不一樣,需要在各個應用做好 cookie 跨域的處理。
  • sessionId 是存儲在 cookie 中的,假如瀏覽器禁止 cookie 或不支持 cookie 怎麼辦? 一般會把 sessionId 跟在 url 參數後面即重寫 url,所以 session 不一定非得需要靠 cookie 實現
  • 移動端對 cookie 的支持不是很好,而 session 需要基於 cookie 實現,所以移動端常用的是 token

使用 JWT 時需要考慮的問題

  • 因爲 JWT 並不依賴 Cookie 的,所以你可以使用任何域名提供你的 API 服務而不需要擔心跨域問題
  • JWT 默認是不加密,但也是可以加密的。生成原始 Token 以後,可以用密鑰再加密一次。
  • JWT 不加密的情況下,不能將祕密數據寫入 JWT。
  • JWT 不僅可以用於認證,也可以用於交換信息。有效使用 JWT,可以降低服務器查詢數據庫的次數。
  • JWT 最大的優勢是服務器不再需要存儲 Session,使得服務器認證鑑權業務可以方便擴展。但這也是 JWT 最大的缺點:由於服務器不需要存儲 Session 狀態,因此使用過程中無法廢棄某個 Token 或者更改 Token 的權限。也就是說一旦 JWT 簽發了,到期之前就會始終有效,除非服務器部署額外的邏輯。
  • JWT 本身包含了認證信息,一旦泄露,任何人都可以獲得該令牌的所有權限。爲了減少盜用,JWT的有效期應該設置得比較短。對於一些比較重要的權限,使用時應該再次對用戶進行認證。
  • JWT 適合一次性的命令認證,頒發一個有效期極短的 JWT,即使暴露了危險也很小,由於每次操作都會生成新的 JWT,因此也沒必要保存 JWT,真正實現無狀態。
  • 爲了減少盜用,JWT 不應該使用 HTTP 協議明碼傳輸,要使用 HTTPS 協議傳輸。

使用加密算法時需要考慮的問題

  • 絕不要以明文存儲密碼
  • 永遠使用 哈希算法 來處理密碼,絕不要使用 Base64 或其他編碼方式來存儲密碼,這和以明文存儲密碼是一樣的,使用哈希,而不要使用編碼。編碼以及加密,都是雙向的過程,而密碼是保密的,應該只被它的所有者知道, 這個過程必須是單向的。哈希正是用於做這個的,從來沒有解哈希這種說法,但是編碼就存在解碼,加密就存在解密。
  • 絕不要使用弱哈希或已被破解的哈希算法,像 MD5 或 SHA1 ,只使用強密碼哈希算法。
  • 絕不要以明文形式顯示或發送密碼,即使是對密碼的所有者也應該這樣。如果你需要 “忘記密碼” 的功能,可以隨機生成一個新的 一次性的(這點很重要)密碼,然後把這個密碼發送給用戶。

只要關閉瀏覽器 ,session 真的就消失了?

不對。瀏覽器關閉時,是不會主動去通知服務器的。之所以會有這種錯覺,是大部分 session 機制都使用會話 cookie 來保存 sessionId,而關閉瀏覽器後這個 cookie 就消失了,再次連接服務器時也就無法找到原來的 session。如果服務器設置的 cookie 被保存在硬盤上,或者使用某種手段改寫瀏覽器發出的 HTTP 請求頭,把原來的 sessionId 發送給服務器,則再次打開瀏覽器仍然能夠打開原來的 session。
恰恰是由於關閉瀏覽器不會導致 session 被刪除,迫使服務器爲 session 設置了一個失效時間,當距離客戶端上一次使用 session 的時間超過這個失效時間時,服務器就認爲客戶端已經停止了活動,纔會把 session 刪除以節省存儲空間。


 

 

 

參考文章

 

 

 

 

 

 

 

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章