Session 與 Token 的差別

TL;DR,這是假議題,這是兩個不一樣目的的設計,應用場景也不同,所以其實沒什麼好比的。

確實 Token 可以拿來當作 Session 用。

但,跨域不得已就算了,同域可以用 Session 的話,幹嘛不用 Session 就好?


前陣子在跟朋友聊這個主題,覺得蠻有趣的,因此決定來寫這篇文章來解惑。

定義

一開始先定義好這篇文章說的 Session 與 Token 為何:

  • Session 是指 RFC 6265 - HTTP State Management Mechanism 提到的應用範例:使用一個唯一碼儲存在 Cookie 中,然後請求到伺服器時,由伺服器去儲存空間取得該唯一碼對應的狀態。
  • Cookie 則是指 HTTP 伺服器在回傳回應時,跟使用者代理(User-Agent,通常是瀏覽器)要求保存某些特定資料,以便下一次的請求再傳送給伺服器確認。因此 Cookie 是儲存在使用者代理。
  • Token 是指 RFC 6749 - The OAuth 2.0 Authorization Framework 所提到的 Access Token 的簡稱,它指的是可以用來存取被保護資源(protected resources)的憑證。

看看相同的地方

公開但不透明

通常 Token 會使用不透明的(Opaque Tokens),意指的是 Token 公開的長相,對使用的角色來說,是沒有意義的。而 Session 的應用也是把狀態細節保存在伺服器上。(但也有透明 Token 的選擇方案,下面在看相異的時候再來看。)

時效的同步性

因 Session 與 Token 通常都會採取不透明的設計,因此會需要在兩個位置保存相同的資訊,一個是使用角色,如瀏覽器;另外一個則是存放資料角色,如伺服器。而在使用角色的情境中,通常不會儲存在持久化空間裡,因此會設定時效;而相對地在伺服器端也會設定時效。使用角色的時效與伺服器的時效會是同步的,這是指其中一邊被移除,另一邊的資料就沒有用了。

不同的地方

不同的地方才是比較的關鍵,以下將會說明它們之間的差異。

網域限制

Session 是基於 Cookie 所設計出來的機制,因此它有與 Cookie 相同的先天限制:跨域無法使用。這是最明顯的不同,這代表如果使用情境是跨域的話,那就完全無法使用 Session。

Token 只有定義可以用來存取被保護的資源,沒有定義網域的限制。這是它們最大的不同。

因為 Session 有這個限制,因此會發現,網路上的文件只會看到 Token 當作 Session 用的案例,但不會看到 Session 當 Token 用的案例。然而,這個做法其實是有問題的,這在文章的最後一段會說明。

透明的設計

Session 在應用上,幾乎都是使用不透明的設計。當然有的框架是可以設定成透明的,例如 Laravel 可以在 config/session.php 設定 Driver 為 cookie,即可在 Cookie 上看到所有 Session 保存的內容,但相信大家都熟讀過資訊安全,知道這樣做是不安全的,因為資訊會被暴露在公開的空間被看光光。

Token 就不一樣了,以 RFC 7515 - JSON Web Signature(JWS,後續將會簡稱為 JWT)來說,簽章只保證了完整性與不可否認性,沒有辦法保護資料不被查閱,因此它是屬於透明的 Token。但它是目前很常見的應用,這點跟 Session 的應用是不同的。

使用場景不同

一開始在定義名詞的時候,已經有說明使用場景了,再搭配上面所提到的內容,可以整理出使用情境差別:

  • Session 是應用在 HTTP 狀態管理,是基於 Cookie 的技術,因此有不能跨網域的限制,通常應用在保存登入狀態,而對應比較機敏的資訊可以透過機制保存在伺服器。
  • Token 是應用在存取被保護資料的憑證,可以跨網域,偶爾可能會使用透明 Token。Token,就是應用在存取被保護資料。

因此可以舉一個兩個同時存在的情境:

  1. 登入 Notion 後,Notion 可以使用 Session 來維持使用者的登入狀態。
  2. 在登入狀態保持的時候,可以透過 OAuth 2.0 授權機制來取得使用者放在 Evernote 的資料。因為這個存取過程與登入狀態是無關的,因此可以同一個使用者授權多個 Evernote,並存取相關的資料。(雖然很麻煩,但辦得到)

把 Token 當 Session 會有什麼問題?

「Token 是否可以取得資料」這件事被用來當作判斷登入狀態的時候,就有點像是把 Token 當作 Session 使用。目前聽到的使用情境都是用在 SPA,前端拿 Token 用於呼叫後端的 API 的授權憑證。

這個做法,最大的問題在於安全性與隱私性。

安全考量

Session 使用的是 Cookie 機制,主要要防的是 CSRF(Cross-Site Request Forgery)攻擊,是很明確的防護目標。另外 Cookie 也有很多參數可以保護其安全,例如 HttpOnlySameSite 等。

而 Token 呢,因為前端會透過 JavaScript(或 WASM)處理 Token 並跟後端 API 互動,這代表 XSS(Cross-Site Scripting)攻擊是有機會接觸到 Token 的。因此 RFC - OAuth 2.0 for Browser-Based Apps 有提到各種儲存方法,以及其對應要注意的風險。

然而,也會有人認為 Cookie 比較安全,所以把 Token 存在 Cookie。但 RFC 在討論各種儲存方法的時候,第一個方法就是 Cookie,然後明確地說:不建議(NOT RECOMMENDED) Token 存在 Cookie,因為會多暴露不必要的風險,應該參考其他的儲存方法。但其他方法還有各種問題要解決。

隱私考量

JWT 很方便,可以透過演算法來保護一份不可變的資料,因此它具備無狀態(stateless)的特色。然而,這份資料通常是不額外加密的,因此就會有隱私相關的問題。

RFC 9068 - JSON Web Token (JWT) Profile for OAuth 2.0 Access Tokens 描述了如何使用 JWT 格式的 Access Token,裡面就有提到禁止 Client 檢視 JWT 的內容,因為 OAuth 2.0 的流程中,應該要把 Access Token 當作是不透明的 Token 來使用。

而無狀態的部分,兩年前寫的文章:淺談 JWT 應用在 API 授權,裡面有提到無狀態的特色跟取代 Session 實作沒有任何關係。

結論

跨域不得已就算了,同域可以用 Session 的話,幹嘛不用 Session 就好?