【網路傳輸】Cookie、Session、Token、JWT

請叫我背影男神發表於2020-11-16

鳴謝:https://juejin.im/post/6844904034181070861

什麼是認證(Authentication)

  • 通俗地講就是驗證當前使用者的身份,證明“你是你自己”(比如:你每天上下班打卡,都需要通過指紋打卡,當你的指紋和系統裡錄入的指紋相匹配時,就打卡成功)
  • 網際網路中的認證:
    • 使用者名稱密碼登入
    • 郵箱傳送登入連結
    • 手機號接收驗證碼
    • 只要你能收到郵箱/驗證碼,就預設你是賬號的主人

什麼是授權(Authorization)

  • 使用者授予第三方應用訪問該使用者某些資源的許可權

    • 你在安裝手機應用的時候,APP 會詢問是否允許授予許可權(訪問相簿、地理位置等許可權)
    • 你在訪問微信小程式時,當登入時,小程式會詢問是否允許授予許可權(獲取暱稱、頭像、地區、性別等個人資訊)
  • 實現授權的方式有:cookie、session、token、OAuth

是憑證(Credentials)

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

    • 在戰國時期,商鞅變法,發明了照身帖。照身帖由官府發放,是一塊打磨光滑細密的竹板,上面刻有持有人的頭像和籍貫資訊。國人必須持有,如若沒有就被認為是黑戶,或者間諜之類的。
    • 在現實生活中,每個人都會有一張專屬的居民身份證,是用於證明持有人身份的一種法定證件。通過身份證,我們可以辦理手機卡/銀行卡/個人貸款/交通出行等等,這就是認證的憑證
    • 在網際網路應用中,一般網站(如掘金)會有兩種模式,遊客模式和登入模式。遊客模式下,可以正常瀏覽網站上面的文章,一旦想要點贊/收藏/分享文章,就需要登入或者註冊賬號。當使用者登入成功後,伺服器會給該使用者使用的瀏覽器頒發一個令牌(token),這個令牌用來表明你的身份,每次瀏覽器傳送請求時會帶上這個令牌,就可以使用遊客模式下無法使用的功能。

什麼是 Cookie

  • HTTP 是無狀態的協議(對於事務處理沒有記憶能力,每次客戶端和服務端會話完成時,服務端不會儲存任何會話資訊):每個請求都是完全獨立的,服務端無法確認當前訪問者的身份資訊,無法分辨上一次的請求傳送者和這一次的傳送者是不是同一個人。所以伺服器與瀏覽器為了進行會話跟蹤(知道是誰在訪問我),就必須主動的去維護一個狀態,這個狀態用於告知服務端前後兩個請求是否來自同一瀏覽器。而這個狀態需要通過 cookie 或者 session 去實現。
  • cookie 儲存在客戶端: cookie 是伺服器傳送到使用者瀏覽器並儲存在本地的一小塊資料,它會在瀏覽器下次向同一伺服器再發起請求時被攜帶併傳送到伺服器上。
  • cookie 是不可跨域的: 每個 cookie 都會繫結單一的域名,無法在別的域名下獲取使用,一級域名和二級域名之間是允許共享使用的(靠的是 domain)

cookie 重要的屬性

屬性說明
name=value鍵值對,設定 Cookie 的名稱及相對應的值,都必須是字串型別
如果值為 Unicode 字元,需要為字元編碼。
如果值為二進位制資料,則需要使用 BASE64 編碼。
domain指定 cookie 所屬域名,預設是當前域名
path指定 cookie 在哪個路徑(路由)下生效,預設是 ‘/’
如果設定為 /abc,則只有 /abc 下的路由可以訪問到該 cookie,如:/abc/read。
maxAgecookie 失效的時間,單位秒。如果為整數,則該 cookie 在 maxAge 秒後失效。如果為負數,該 cookie 為臨時 cookie ,關閉瀏覽器即失效,瀏覽器也不會以任何形式儲存該 cookie 。如果為 0,表示刪除該 cookie 。預設為 -1。
比 expires 好用。
expires過期時間,在設定的某個時間點後該 cookie 就會失效。一般瀏覽器的 cookie 都是預設儲存的,當關閉瀏覽器結束這個會話的時候,這個 cookie 也就會被刪除
secure該 cookie 是否僅被使用安全協議傳輸。安全協議有 HTTPS,SSL等,在網路上傳輸資料之前先將資料加密。預設為false。
當 secure 值為 true 時,cookie 在 HTTP 中是無效,在 HTTPS 中才有效。
httpOnly如果給某個 cookie 設定了 httpOnly 屬性,則無法通過 JS 指令碼 讀取到該 cookie 的資訊,但還是能通過 Application 中手動修改 cookie,所以只是在一定程度上可以防止 XSS 攻擊,不是絕對的安全

什麼是 Session

  • session 是另一種記錄伺服器和客戶端會話狀態的機制

  • session 是基於 cookie 實現的,session 儲存在伺服器端,sessionId 會被儲存到客戶端的cookie 中
    在這裡插入圖片描述

  • session 認證流程:

    • 使用者第一次請求伺服器的時候,伺服器根據使用者提交的相關資訊,建立對應的 Session
    • 請求返回時將此 Session 的唯一標識資訊 SessionID 返回給瀏覽器
    • 瀏覽器接收到伺服器返回的 SessionID 資訊後,會將此資訊存入到 Cookie 中,同時 Cookie 記錄此 SessionID 屬於哪個域名
    • 當使用者第二次訪問伺服器的時候,請求會自動判斷此域名下是否存在 Cookie 資訊,如果存在自動將 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. 服務端收到請求,去驗證使用者名稱與密碼
  3. 驗證成功後,服務端會簽發一個 token 並把這個 token 傳送給客戶端
  4. 客戶端收到 token 以後,會把它儲存起來,比如放在 cookie 裡或者 localStorage 裡
  5. 客戶端每次向服務端請求資源的時候需要帶著服務端簽發的 token
  6. 服務端收到請求,然後去驗證客戶端請求裡面帶著的 token ,如果驗證成功,就向客戶端返回請求的資料
  • 每一次請求都需要攜帶 token,需要把 token 放到 HTTP 的 Header 裡
  • 基於 token 的使用者認證是一種服務端無狀態的認證方式,服務端不用存放 token 資料。用解析 token 的計算時間換取 session 的儲存空間,從而減輕伺服器的壓力,減少頻繁的查詢資料庫
  • token 完全由應用管理,所以它可以避開同源策略

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 來在伺服器端儲存一些狀態
  • 所謂 Session 認證只是簡單的把 User 資訊儲存到 Session 裡,因為 SessionID 的不可預測性,暫且認為是安全的。而 Token ,如果指的是 OAuth Token 或類似的機制的話,提供的是 認證 和 授權 ,認證是針對使用者,授權是針對 App 。其目的是讓某 App 有權利訪問某使用者的資訊。這裡的 Token 是唯一的。不可以轉移到其它 App上,也不可以轉到其它使用者上。Session 只提供一種簡單的認證,即只要有此 SessionID ,即認為有此 User 的全部權利。是需要嚴格保密的,這個資料應該只儲存在站方,不應該共享給其它網站或者第三方 App。所以簡單來說:如果你的使用者資料可能需要和第三方共享,或者允許第三方呼叫 API 介面,用 Token 。如果永遠只是自己的網站,自己的 App,用什麼就無所謂了

什麼是 JWT

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

生成 JWT

jwt.io/
www.jsonwebtoken.io/

JWT 的原理

在這裡插入圖片描述

  • JWT 認證流程:

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

JWT 的使用方式

  • 客戶端收到伺服器返回的 JWT,可以儲存在 Cookie 裡面,也可以儲存在 localStorage。

方式一

  • 當使用者希望訪問一個受保護的路由或者資源的時候,可以把它放在 Cookie 裡面自動傳送,但是這樣不能跨域,所以更好的做法是放在 HTTP 請求頭資訊的 Authorization 欄位裡,使用 Bearer 模式新增 JWT。
GET /calendar/v1/events
Host: api.example.com
Authorization: Bearer <token>
  • 使用者的狀態不會儲存在服務端的記憶體中,這是一種 無狀態的認證機制
  • 服務端的保護路由將會檢查請求頭 Authorization 中的 JWT 資訊,如果合法,則允許使用者的行為。
  • 由於 JWT 是自包含的,因此減少了需要查詢資料庫的需要
  • JWT 的這些特性使得我們可以完全依賴其無狀態的特性提供資料 API 服務,甚至是建立一個下載流服務。
  • 因為 JWT 並不使用 Cookie ,所以你可以使用任何域名提供你的 API 服務而不需要擔心跨域資源共享問題(CORS)

方式二

  • 跨域的時候,可以把 JWT 放在 POST 請求的資料體裡。

方式三

  • 通過 URL 傳輸
http://www.example.com/user?token=xxx

專案中使用 JWT

https://github.com/yjdjiayou/jwt-demo

Token 和 JWT 的區別

相同:

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

區別:

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

常見的前後端鑑權方式

  1. Session-Cookie
  2. Token 驗證(包括 JWT,SSO)
  3. OAuth2.0(開放授權)

常見的加密演算法

在這裡插入圖片描述

  • 雜湊演算法(Hash Algorithm)又稱雜湊演算法、雜湊函式、雜湊函式,是一種從任何一種資料中建立小的數字“指紋”的方法。雜湊演算法將資料重新打亂混合,重新建立一個雜湊值。

  • 雜湊演算法主要用來保障資料真實性(即完整性),即發信人將原始訊息和雜湊值一起傳送,收信人通過相同的雜湊函式來校驗原始資料是否真實。

  • 雜湊演算法通常有以下幾個特點:

    • 正像快速:原始資料可以快速計算出雜湊值

    • 逆向困難:通過雜湊值基本不可能推匯出原始資料

    • 輸入敏感:原始資料只要有一點變動,得到的雜湊值差別很大

    • 衝突避免:很難找到不同的原始資料得到相同的雜湊值,宇宙中原子數大約在 10 的 60 次方到 80 次方之間,所以 2 的 256 次方有足夠的空間容納所有的可能,演算法好的情況下衝突碰撞的概率很低:

      • 2 的 128 次方為 340282366920938463463374607431768211456,也就是 10 的 39 次方級別
      • 2 的 160 次方為 1.4615016373309029182036848327163e+48,也就是 10 的 48 次方級別
      • 2 的 256 次方為 1.1579208923731619542357098500869 × 10 的 77 次方,也就是 10 的 77 次方

注意:

  1. 以上不能保證資料被惡意篡改,原始資料和雜湊值都可能被惡意篡改,要保證不被篡改,可以使用RSA 公鑰私鑰方案,再配合雜湊值。
  2. 雜湊演算法主要用來防止計算機傳輸過程中的錯誤,早期計算機通過前 7 位資料第 8 位奇偶校驗碼來保障(12.5% 的浪費效率低),對於一段資料或檔案,通過雜湊演算法生成 128bit 或者 256bit 的雜湊值,如果校驗有問題就要求重傳。

常見問題

使用 cookie 時需要考慮的問題

  • 因為儲存在客戶端,容易被客戶端篡改,使用前需要驗證合法性
  • 不要儲存敏感資料,比如使用者密碼,賬戶餘額
  • 使用 httpOnly 在一定程度上提高安全性
  • 儘量減少 cookie 的體積,能儲存的資料量不能超過 4kb
  • 設定正確的 domain 和 path,減少資料傳輸
  • cookie 無法跨域
  • 一個瀏覽器針對一個網站最多存 20 個Cookie,瀏覽器一般只允許存放 300 個Cookie
  • 移動端對 cookie 的支援不是很好,而 session 需要基於 cookie 實現,所以移動端常用的是 token

使用 session 時需要考慮的問題

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

使用 token 時需要考慮的問題

  • 如果你認為用資料庫來儲存 token 會導致查詢時間太長,可以選擇放在記憶體當中。比如 redis 很適合你對 token 查詢的需求。
  • token 完全由應用管理,所以它可以避開同源策略
  • token 可以避免 CSRF 攻擊(因為不需要 cookie 了)
  • 移動端對 cookie 的支援不是很好,而 session 需要基於 cookie 實現,所以移動端常用的是 token

使用 JWT 時需要考慮的問題

  • 因為 JWT 並不依賴 Cookie 的,所以你可以使用任何域名提供你的 API 服務而不需要擔心跨域資源共享問題(CORS)
  • 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 共享方案

1. session 複製

  • 任何一個伺服器上的 session 發生改變(增刪改),該節點會把這個 session 的所有內容序列化,然後廣播給所有其它節點,不管其他伺服器需不需要 session ,以此來保證 session 同步

優點: 可容錯,各個伺服器間 session 能夠實時響應。

缺點: 會對網路負荷造成一定壓力,如果 session 量大的話可能會造成網路堵塞,拖慢伺服器效能。

2. 粘性 session /IP 繫結策略

  • 採用 Ngnix 中的 ip_hash 機制,將某個 ip的所有請求都定向到同一臺伺服器上,即將使用者與伺服器繫結。 使用者第一次請求時,負載均衡器將使用者的請求轉發到了 A 伺服器上,如果負載均衡器設定了粘性 session 的話,那麼使用者以後的每次請求都會轉發到 A 伺服器上,相當於把使用者和 A 伺服器粘到了一塊,這就是粘性 session 機制。

優點: 簡單,不需要對 session 做任何處理。

缺點: 缺乏容錯性,如果當前訪問的伺服器發生故障,使用者被轉移到第二個伺服器上時,他的 session 資訊都將失效。

適用場景: 發生故障對客戶產生的影響較小;伺服器發生故障是低概率事件

實現方式: 以 Nginx 為例,在 upstream 模組配置 ip_hash 屬性即可實現粘性 session。

3. session 共享(常用)

  • 使用分散式快取方案比如 Memcached 、Redis 來快取 session,但是要求 Memcached 或 Redis 必須是叢集

  • 把 session 放到 Redis 中儲存,雖然架構上變得複雜,並且需要多訪問一次 Redis ,但是這種方案帶來的好處也是很大的:

    • 實現了 session 共享;
    • 可以水平擴充套件(增加 Redis 伺服器);
    • 伺服器重啟 session 不丟失(不過也要注意 session 在 Redis 中的重新整理/失效機制);
    • 不僅可以跨伺服器 session 共享,甚至可以跨平臺(例如網頁端和 APP 端)

在這裡插入圖片描述

4. session 持久化

  • 將 session 儲存到資料庫中,保證 session 的持久化

優點: 伺服器出現問題,session 不會丟失

缺點: 如果網站的訪問量很大,把 session 儲存到資料庫中,會對資料庫造成很大壓力,還需要增加額外的開銷維護資料庫。

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

不對。對 session 來說,除非程式通知伺服器刪除一個 session,否則伺服器會一直保留,程式一般都是在使用者做 log off 的時候發個指令去刪除 session。然而瀏覽器從來不會主動在關閉之前通知伺服器它將要關閉,因此伺服器根本不會有機會知道瀏覽器已經關閉,之所以會有這種錯覺,是大部分 session 機制都使用會話 cookie 來儲存 session id,而關閉瀏覽器後這個 session id 就消失了,再次連線伺服器時也就無法找到原來的 session。如果伺服器設定的 cookie 被儲存在硬碟上,或者使用某種手段改寫瀏覽器發出的 HTTP 請求頭,把原來的 session id 傳送給伺服器,則再次開啟瀏覽器仍然能夠開啟原來的 session。恰恰是由於關閉瀏覽器不會導致 session 被刪除,迫使伺服器為 session 設定了一個失效時間,當距離客戶端上一次使用 session 的時間超過這個失效時間時,伺服器就認為客戶端已經停止了活動,才會把 session 刪除以節省儲存空間。

專案地址

在專案中使用 JWT

https://github.com/yjdjiayou/jwt-demo

相關文章