為什麼要關心 SameSite#
Cookie 是網頁服務維持狀態的關鍵機制,常見於登入、購物車、廣告追蹤等情境。隨著 Cookie 被廣泛使用,隱私與安全議題也跟著浮現,SameSite 屬性正是為了在原本「同 Domain 自動帶 Cookie」的行為上加一層守門,讓瀏覽器能依據情境決定是否要附帶。
Chrome 從 84 版開始,把沒有設定 SameSite 的 Cookie 預設為
Lax。若你的服務依賴第三方 Cookie(Third-Party Cookie)卻沒有顯式宣告 SameSite,行為可能會悄悄改變。
First-Party 與第三方 Cookie(Third-Party Cookie)#
每個 Cookie 都會綁定一個 Domain,這個 Domain 來自 Set-Cookie 寫入時所屬的網址。在使用者當下瀏覽的網址視角下:
- 若 Cookie 的 Domain 與目前頁面相符,就是 First-Party。
- 若兩者不同,那這個 Cookie 對目前頁面而言就是 Third-Party。
舉例來說,瀏覽 a.com 時頁面對 third-party.com 發出請求,瀏覽器會自動帶上對應的 Cookie。後續換到 b.com 時,只要再請求 third-party.com,那把 Cookie 仍會被帶過去——對 a.com 與 b.com 來說,那都是 Third-Party Cookie。但如果使用者直接造訪 third-party.com,同一個 Cookie 對該頁面就是 First-Party。
同源(Same-Origin)與同站(Same-Site)#
SameSite 比對的是「Site」,不是「Origin」。兩者經常被混用,但定義不同:
Origin#
Origin 由 Scheme、Host、Port 組成。三者完全相同才算 Same-Origin,任何一項不同就是 Cross-Origin。
Site#
Same-Site 牽涉 eTLD(Effective Top-Level Domain)。所有 eTLD 列在 Public Suffix List ↗,Site 由 eTLD 加上前一段網域組成:
github.io在 Public Suffix List 內,所以a.github.io與b.github.io屬於不同 Site(Cross-Site)。.com在清單內、example.com不在,因此a.example.com與b.example.com同樣屬於example.com這個 Site(Same-Site)。
Site 不包含 Port,Port 不同也算 Same-Site。
沒有 SameSite 會發生什麼事#
CSRF (Cross-Site Request Forgery)#
假設使用者已登入 example.com 並取得 Cookie,當他造訪惡意網站 evil.com,惡意頁面可以對 example.com/pay?amount=1000 發出 POST 請求。瀏覽器照舊把 example.com 的 Cookie 帶上,伺服器無從分辨這次請求究竟是使用者主動發起,還是被偷渡——使用者就在不知情下完成了一筆轉帳。
為何 Cookie 自己無法解決#
過去的 Cookie 沒有「只在 First-Party 環境送出」的開關,所以只要 Domain 相符,瀏覽器一律附帶。Server 沒辦法分辨來源、Client 也浪費流量送出沒必要的 Cookie。SameSite 屬性正是補足這塊。
三種 SameSite 設定#
SameSite 有三個值。沒設定時不同瀏覽器會走不同預設行為,目前 Chrome 走 Lax。
Strict#
只在 First-Party 情境帶 Cookie。語意最嚴格,但會帶來體驗問題:使用者在 example.com 點擊 fb.com 的貼文連結時,即便他先前登入過 fb.com,導向後也不會帶 Cookie,會被視為未登入。
Strict 適合付款、刪除這類「動作型」的 Cookie。
Lax#
Lax 在以下情境即便是 Cross-Site 仍會送出 Cookie:
- 直接在網址列輸入網址。
- 點擊
<a href="...">連結。 - GET 表單送出
<form method="GET">。 <link rel="prerender" href="...">之類的背景預載。
這些情境的共通點:都是 GET,並且會觸發頁面導航(Navigation)。如此一來既能避開 Strict 強制重新登入的不便,也不會在使用者瀏覽其他網站時不小心送出 Cookie。
Lax + POST 的彈性#
為了避免破壞既有登入流程,Chrome 在 SameSite=Lax 上保留了一段過渡期:Cookie 設定後的兩分鐘內,只要是觸發 Top-Level 導航的請求(例如 POST 表單送出),不論 Method 都會帶上 Cookie。詳情見 Chromium 上的討論串
↗。
None#
想要在 Cross-Site 情境送出 Cookie 必須宣告 SameSite=None; Secure。換句話說,現在連在開發機測試第三方 Cookie 也得先準備 https://localhost。
Set-Cookie: name=value; SameSite=None; Secure另外用 XHR 或 Fetch 發出 Cross-Origin 請求時,要額外設定 withCredentials: true 才會帶 Cookie,且 Response 的 Set-Cookie 才會生效;伺服器端也必須回 Access-Control-Allow-Credentials: true,前端 JavaScript 才能讀到 Response。
給尚未支援的瀏覽器留後路#
並非每個瀏覽器都採用最新的 SameSite 規則,過渡期可考慮兩種 Workaround。
同時設兩份 Cookie#
幾乎能涵蓋所有瀏覽器,缺點是 Cookie 會雙倍:
Set-Cookie: name=value; SameSite=None; Secure
Set-Cookie: name-legacy=value; Secure伺服器讀取時依序判斷:
if (req.cookies["name"]) {
// 有新版就用新的
cookieVal = req.cookies["name"];
} else if (req.cookies["name-legacy"]) {
// 否則 fallback 舊的
cookieVal = req.cookies["name-legacy"];
}依 User Agent 切換 Set-Cookie#
伺服器以 Request 的 User Agent 判斷瀏覽器,再決定要送哪一種 Set-Cookie。好處是不必修改解析端,但 UA 字串可變因素多,誤判機率較高。
重點回顧#
- 沒指定 SameSite 的 Cookie 會被當作
SameSite=Lax,在 Cross-Site 情境下無法送出。 - 想在 Cross-Site 情境送 Cookie,必須明確宣告
SameSite=None; Secure。 - 可以用 SameSite sandbox ↗ 確認當前瀏覽器是否已套用最新規則。
原文出處#
- 原書/iThome:https://ithelp.ithome.com.tw/articles/10251288