Cookie === ### 參數 | Domain | Path | Expires / Max-age | | -------- | -------- | -------- | | 哪些網域可以存取這個 cookie | 哪些路徑可以存取這個 cookie | 設定 cookie 的有效期限 | | Secure | HttpOnly | SameSite | | -------- | -------- | -------- | | 讓 cookie 只能透過 https 傳遞 | 防止 JavaScript 存取 cookie | 防止 cookie 以跨站方式傳送 | ### SameSite 參數的設定 || strict | lax | none | | -------- | -------- | -------- | -------- | | 跨域 | X | X | O | | gmaill | X | O | O | 介紹 --- Cookie 是儲存在瀏覽器的小段文字資料,通常由伺服器透過 `Set-Cookie` header 傳遞給瀏覽器。瀏覽器收到後會將 cookie 儲存起來,並在之後的請求回傳 cookie 至同樣的伺服器。 ### 用途 Cookie 常見用途是認證身份,例如登入狀態、購物車等,也被應用於追蹤使用者及廣告上。 Cookie 也被用於客戶端的儲存方式,但由於 cookie 會被附加在每一次的 request 之中,可能會影響效能,所以如果是不需要記錄在 server 的資訊,可以改用 storage API。 ### Set-Cookie header 伺服器(Server)可以在 HTTP response 中回傳 `Set-Cookie` header 告訴瀏覽器要設定 cookie。 ``` Set-Cookie: [cookie名稱]=[cookie值] ``` 瀏覽器看到 `Set-Cookie` header 便會將 cookie 儲存起來,之後對同一個域名(domain)發送 HTTP request 的時候,瀏覽器就會將 cookie 帶在 HTTP request 的 Cookie header 裡。 Request 中的 cookie header 會是 `[cookie名稱]=[cookie值]` 的形式。 ``` Cookie: [cookie1]=[value1]; [cookie2]=[value2] ``` [使用](https://codepen.io/betty-hu/pen/qBVLYzV?editors=1010) --- ### 使用 JavaScript 讀取 Cookie 在 JavaScript 中,可以使用 `document.cookie` 讀取 cookie 。 ```js console.log(document.cookie); ``` 讀取出來的 `document.cookie` 會是字串,是將網域底下所有 cookie 用分號串接的結果,其中每個 cookie 都是 `[cookie名稱]=[cookie值]` 的形式。 例如:`name=John; gender=male` 表示這個網域底下有兩個 cookie:`name` 和 `gender`,其中 `name` 的值是 `John`,而 `gender` 的值是 `male`。 如何讀取特定 cookie 的值,例如:如何從 `name=John; gender=male` 的字串得到 `name` 和 `gender` 這兩個 cookie 的值?試著自己寫一個函式 parse,或是利用較為成熟的第三方套件,如:cookie等。 ```jsx // Reference: https://stackoverflow.com/questions/10730362/get-cookie-by-name function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } ``` ```jsx getCookie('name') ``` ### 使用 JavaScrip 寫入 Cookie 使用 JavaScript 寫入 cookie 的方式是 `document.cookie = 'key=value;'`。 雖然以下使用 document.cookie = ...,但是並不會整個 cookie 都被覆蓋,只有指定的 key 會被更新。 如下面例子,cookie3 被新增的同時,原本的 cookie1 和 cookie2 還會保留。 ```jsx document.cookie = 'cookie=value'; ``` Cookie 的參數 --- Cookie 除了名稱和值以外,通常需要設定其他額外參數。 新增參數的方式,用分號區隔各個參數,例如: ``` user=John; path=/; expires=Tue, 19 Jan 2038 03:14:07 GMT ``` 使用 `domain` 和 `Path` 指定 cookie 的可用範圍,使用 `Expires` 和 `Max-Age` 控制 cookie 的有效期限,而 `HttpOnly`、`Secure`、和 `SameSite` 則是安全性相關參數。 ### Domain ``` domain=example.com ``` `domain` 指定哪些網域可以存取這個 cookie。 預設值是當前網域,但是**不包含其子網域**。 例如在 `example.com` 底下設置的 cookie 不指定 `domain` 的情況,只有 `example.com` 可以存取此 cookie,但子網域如 `subdomain.example.com` 則無法存取此 cookie。 如果想要讓子網域存取 cookie,必須明確設定 `domain` 參數。 例如:當一個 cookie 指定 `domain=example.com` 時,包含 `example.com` 以及他的子網域 `subdomain.example.com` 都能夠存取這個 cookie。 ### Path ``` path=/admin ``` `path` 指定哪些路徑可以存取這個 cookie。 例如:假設 domain 是 `example.com`,且 path=/admin,則 `example.com/admin` 或是子路徑 `example.com/admin/settings` 都可以存取此 cookie,但 `example.com` 或是 `example.com/home` 則無法存取此 cookie。 Path 的預設值是當前的路徑。 一般而言,認證用途的 cookie 會設成 `path=/`,讓全站可以存取此 cookie,如此一來不管使用者在網站的哪個路徑,伺服器(Server)都能認出使用者的身份。 範例:[全域音樂](https://codepen.io/betty-hu/pen/VYeyMRw) ```js function getCookie(name) { const value = `; ${document.cookie}`; const parts = value.split(`; ${name}=`); if (parts.length === 2) return parts.pop().split(';').shift(); } const audio = document.querySelector("audio"); // 離開網頁 window.addEventListener('visibilitychange', function () { if (document.visibilityState === 'hidden') { document.cookie = `key=${audio.currentTime};path=/;` // 寫入 cookie // path=/; 作用範圍為全域 } }); // 載入網頁 window.addEventListener('load', function () { audio.currentTime = getCookie("key"); // 讀取 cookie }); ``` ### Expires, Max-age `expires`, `max-age` 設定 cookie 的有效期限。 如果沒有額外設定 `expires` 或是 `max-age` 參數,當瀏覽器關閉之後,儲存在瀏覽器的 cookie 便會消失,這就是所謂的 **session cookie**。 如果希望瀏覽器關掉之後 cookie 還是會被儲存下來,就必須設定 `expires` 或是 `max-age`。 `expires` 是 UTC 格式表示的有效期限,在 JavaScript 中可用 `date.toUTCString()` 取得: ``` cookie=value; expires=Tue, 19 Jan 2038 03:14:07 GMT ``` `max-age` 表示從設定開始算之後幾秒之內 cookie 是有效的: ``` cookie=value; max-age=3600 ``` ### Secure `Secure` 設定 cookie 只能透過 https 傳遞。 Cookie 預設不區分 http 或是 https。當設定 `http://example.com` 的 cookie 時,`https://example.com` 也能看到同樣的 cookie。 如果 cookie 設了 `secure` 參數,只有透過 https 存取這個網站才能存取這個 cookie;透過 http 存取這個網站就看不到這個 cookie。 這個參數的作用在於保護 cookie 只能在 https 傳遞。話雖如此,仍是不能將敏感資訊儲存在 cookie 中。 ### HttpOnly `HttpOnly` 防止 JavaScript 存取 cookie。 當 cookie 設置 `httpOnly` 屬性,JavaScript 就不能存取這個 cookie,但是瀏覽器在發送 request 時,還是會幫你帶在 request header 裡面。 這是為了安全性考量,如果 JavaScript 能夠存取這個 cookie 就有受到 XSS Attack (Cross-Site Scripting,跨站腳本攻擊) 的風險。 #### XSS Attack (跨站腳本攻擊) 將惡意的 JavaScript 程式碼透過表單方式上傳到 server,這份表單資料在前端呈現時,惡意的 JavaScript 程式碼會被當成是 HTML 的一部分被執行。 假設攻擊者能夠執行 JavaScript,便能輕易地存取 `document.cookie`,就能夠竊取受害者登入的 cookie,並且以受害者的身份做惡意的操作。 ### SameSite `Samesite` 防止 cookie 以跨站方式傳送,避免 CSRF (Cross-Site Request Forgery,跨站請求偽造) 攻擊。 #### CSRF (Cross-Site Request Forgery,跨站請求偽造) 攻擊 在受害者已登入的狀態,假借受害者的身份進行惡意操作。 例如:將受害者銀行裡的錢轉到攻擊者的帳戶。 #### 具體如何進行 CSRF 攻擊? 假設含有使用者的機敏資訊網站叫做 `bank.com`,使用者成功登入 `bank.com` 後,會收到加密過代表使用者身份的 cookie,後續請求(request)都會帶有這個 cookie。 因為 cookie 經過加密無法輕易偽造,所以伺服器(server)認證請求(request)來自使用者本人,也會同意轉帳等敏感操作。 攻擊者透過釣魚信件方式讓受害者不小心進入攻擊者的網站 `evil.com`,其內包含一小段發送表單的代碼,會把 `bank.com` 的錢轉到攻擊者的戶頭。 因為送往 `bank.com` 的 request 帶有表示身份的 cookie,包含發送表單的 POST request,所以 server 認為這個 request 沒有問題,錢就會轉給攻擊者。 > 通常會在表單中多帶伺服器(server)產生的 CSRF token 防範此攻擊,與 `SameSite` 兩防護措施同時並行。 > 在 `evil.com` 發送 `bank.com` 請求的情境下,`back.com` 的 cookie 就是所謂的第三方 cookie。 #### 跨域請求 當請求的網域和網址列中的網域不同時,它就是一個跨域請求。在範例中,瀏覽器的網址列的網域是 `evil.com`,但是送往 `bank.com` 網域的 POST request,因此這個 POST 是跨域請求。 跨域請求攜帶 cookie 就有遭受 CSRF 攻擊的風險。 #### `SameSite` 參數如何防範 CSRF 攻擊 `SameSite` 共有三種不同設定,分別對應不同的安全性層級: - `SameSite = strict` `Strict` 表示 request 的網域必須跟網址列中的網域相同,才會發送這個 cookie。 以範例而言,發送轉帳的 POST 請求時,因為屬於跨域請求的關係,並不會攜帶表示身份的 cookie,就不會受到 CSRF 攻擊。 如果是從 email 導過去 `bank.com`,`SameSite` cookie 也不會被發送給 server。這可以保護使用者不會因為點了釣魚信件的連結就轉帳給攻擊者。 缺點是,即使信件確實來自 `bank.com`,也一律需要使用者重新登入,相對而言較為不方便。所以這個設置比較適合用於敏感操作,例如轉帳、修改密碼等。 - `SameSite = lax` `Lax` cookie 和 `strict` 相比限制較為寬鬆。瀏覽器發送跨網域的 request 一樣不會攜帶 `lax` cookie,除了導向目標網址的 GET request。 但是,單純地在瀏覽器開啟連結,或是從 email 點開連結就會攜帶 `lax` cookie。 注意:[從Chrome 76 開始,預設值為 SameSite=lax](https://chromestatus.com/feature/5088147346030592)。意味第三方 cookie 在沒有明確設定 `SameSite` 的情況下會失效。 - `SameSite = none` 跨域的情況下仍會送出 cookie。 注意:[從Chrome 80 開始,使用這個選項必須同時開啟 Secure 參數](https://chromestatus.com/feature/5633521622188032)。 如果你的產品仰賴第三方 cookie,例如廣告、iframe 嵌入套件等,應該要使用這個選項。 第三方 Cookie(Third-Party Cookie) --- 網頁需要向其他網域請求資源,例如:使用 `<img src="...">` 的方式嵌入一張其他網域的圖片。這些 request 可以攜帶 cookie,攜帶哪些 cookie 主要根據資源的網域。 #### 第一方 cookie (first-party cookie) 使用者瀏覽 `example.com`,其中包含圖片 `<img src="https://example.com/image.png">`,此時攜帶的 cookie 會是 `example.com` 底下的 cookie。 因為請求的網域和網址列的網域同樣是 `example.com`,這是相同網域的請求。 此時 `example.com` 底下的 cookie 又稱作第一方 cookie (first-party cookie)。 #### 第三方 cookie (third-party cookie) 如果 `example.com` 包含另一張圖片 `<img src="https://ad.com/image.png">`,網域是 `ad.com`,此時攜帶的 cookie 就會是 `ad.com` 底下的 cookie。 因為 `ad.com` 不同於網址列的 `example.com`,所以這是跨域請求。 此時 `ad.com` 底下的 cookie 又稱作第三方 cookie (third-party cookie)。 #### 第三方 cookie 為何重要? 第三方 cookie 能夠跨網域追蹤。 > 例如: > > `example.com` 發出 `ad.com` 請求時,會攜帶 `ad.com` 的 cookie。 > > 如果同時另一個網域 `anothersite.com` 也來請求 `ad.com` 的資源,也會攜帶同樣的 cookie。 > > 如果這個 cookie 用來表示使用者 id,則對 `ad.com` 而言,都會知道兩個網站的造訪者。 > > 這是廣告追蹤的原理。 這就是為何對第三方 cookie 的限制增多:在 2020 年,在 Chrome 必須要明確標示 `SameSite=None; Secure`,否則預設情況 `SameSite=Lax`,第三方 cookie 不會被發送的。而 Safari 完全禁止第三方 cookie。 Session 與 Cookie差別 --- http 本身是無狀態(Stateless)協議,可以在 Client 與 Server 兩端進行溝通,但是無法紀錄網路上的行為。 如果登入一個網站,每次訪問該網站時,需要將登入帳密再輸入一次,或是現在頁面上的資料填到一半,不小心把網頁關掉,重開頁面只好再重新輸入一次,使用起來非常不便。 Cookie 和 Session 因此誕生,解決無紀錄狀態的問題。 ### Cookie 由 Server 送給瀏覽器的文檔資料。瀏覽器儲存,並在瀏覽器下一次發送要求時,將它送回原本送來的 Server。 實例說明:網站可以透過 Cookie 的方式,紀錄之前輸入的帳號密碼,省去每次重打帳密的不便,只要 Cookie 尚未到期,瀏覽器會傳送該 Cookie 給伺服器作驗證憑據。 #### 特性 - 特定網域:只針對原本的網域(domain)起作用。 - 生命期限: 到了設定的生命期限後失效。 - 在向該網域的 server 發送請求時,也會一併帶進該請求。 #### 缺點: - cookie 的所有數據在 Client 端可以被修改,重要數據不能存放在 cookie 中。 - 如果 cookie 數據字段太多會影響傳輸效率。 為了解決這些問題,產生 session。session 的數據保留在 Server 端。 ### Session Session 紀錄在 server 端的使用者訊息,在用戶完成身分認證後,存下所需的用戶資料,產生一組對應的 ID 存入 cookie 後傳回用戶端。 詳細資料 --- - [什麼是 Cookie?如何用 JS 讀取/修改 document.cookie?](https://shubo.io/cookies/) - [[XSS 1] 從攻擊自己網站學 XSS (Cross-Site Scripting)](https://medium.com/hannah-lin/%E5%BE%9E%E6%94%BB%E6%93%8A%E8%87%AA%E5%B7%B1%E7%B6%B2%E7%AB%99%E5%AD%B8-xss-cross-site-scripting-%E5%8E%9F%E7%90%86%E7%AF%87-fec3d1864e42) - [解釋 Cookie 的特性](https://blog.miniasp.com/post/2008/02/22/Explain-HTTP-Cookie-in-Detail) - [Cookies - SameSite Attribute](https://ithelp.ithome.com.tw/articles/10251288) - [CORS 是什麼? 如何設定 CORS?](https://shubo.io/what-is-cors/)