由於 HTTP 協議是無狀態的,因此好久之前的網站是沒有登陸這個概念的,直到網景發明 cookie 之後,網站纔開始利用 cookie 記錄用戶的登陸狀態。cookie 是個好東西,但它很不安全,其中一個緣由是由於 cookie 最初被設計成了容許在第三方網站發起的請求中攜帶,CSRF 攻擊就是利用了 cookie 的這一「弱點」,若是你不瞭解 CSRF,請移步別的地方學習一下再來。php
當咱們在瀏覽器中打開 a.com 站點下的一個網頁後,這個頁面後續能夠發起其它的 HTTP 請求,根據請求附帶的表現不一樣,這些請求能夠分爲兩大類:html
1. 異步請求(不會改變當前頁面,也不會打開新頁面),好比經過 <script>、<link>、<img>、<iframe> 等標籤發起的請求,還有經過各類發送 HTTP 請求的 DOM API(XHR,fetch,sendBeacon)發起的請求。java
2. 同步請求(可能改變當前頁面,也可能打開新頁面),好比經過對 <a> 的點擊,對 <form> 的提交,還有改變 location.href,調用 window.open() 等方式產生的請求。node
上面說的同步和異步並非正式術語,只是我我的的一種區分方式。git
這些由當前頁面發起的請求的 URL 不必定也是 a.com 上的,可能有 b.com 的,也可能有 c.com 的。咱們把發送給 a.com 上的請求叫作第一方請求(first-party request),發送給 b.com 和 c.com 等的請求叫作第三方請求(third-party request),第三方請求和第一方請求同樣,都會帶上各自域名下的 cookie,因此就有了第一方 cookie(first-party cookie)和第三方 cookie(third-party cookie)的區別。上面提到的 CSRF 攻擊,就是利用了第三方 cookie 。github
防止 CSRF 攻擊的辦法已經有 CSRF token 校驗和 Referer 請求頭校驗。爲了從源頭上解決這個問題,Google 起草了一份草案來改進 HTTP 協議,那就是爲 Set-Cookie 響應頭新增 SameSite 屬性,它用來標明這個 cookie 是個「同站 cookie」,同站 cookie 只能做爲第一方 cookie,不能做爲第三方 cookie。SameSite 有兩個屬性值,分別是 Strict 和 Lax,下面分別講解:express
嚴格模式,代表這個 cookie 在任何狀況下都不可能做爲第三方 cookie,絕無例外。好比說假如 b.com 設置了以下 cookie:api
Set-Cookie: foo=1; SameSite=Strict Set-Cookie: bar=2
你在 a.com 下發起的對 b.com 的任意請求中,foo 這個 cookie 都不會被包含在 Cookie 請求頭中,但 bar 會。舉個實際的例子就是,假如淘寶網站用來識別用戶登陸與否的 cookie 被設置成了 SameSite=Strict,那麼用戶從百度搜索頁面甚至天貓頁面的連接點擊進入淘寶後,淘寶都不會是登陸狀態,由於淘寶的服務器不會接受到那個 cookie,其它網站發起的對淘寶的任意請求都不會帶上那個 cookie。瀏覽器
寬鬆模式,比 Strict 放寬了點限制:假如這個請求是我上面總結的那種同步請求(改變了當前頁面或者打開了新頁面)且同時是個 GET 請求(由於從語義上說 GET 是讀取操做,比 POST 更安全),則這個 cookie 能夠做爲第三方 cookie。好比說假如 b.com 設置了以下 cookie:安全
Set-Cookie: foo=1; SameSite=Strict Set-Cookie: bar=2; SameSite=Lax Set-Cookie: baz=3
當用戶從 a.com 點擊連接進入 b.com 時,foo 這個 cookie 不會被包含在 Cookie 請求頭中,但 bar 和 baz 會,也就是說用戶在不一樣網站之間經過連接跳轉是不受影響了。但假如這個請求是從 a.com 發起的對 b.com 的異步請求,或者頁面跳轉是經過表單的 post 提交觸發的,則 bar 也不會發送。
該用哪一種模式,要看你的需求。好比你的網站是一個少數人使用的後臺管理系統,全部人的操做方式都是從本身瀏覽器的收藏夾裏打開網址,那我看用 Strict 也無妨。若是你的網站是微博,用了 Strict 會這樣:有人在某個論壇裏發了帖子「快看這個微博多搞笑 http://weibo.com/111111/aaaaaa」,結果下面人都回復「打不開啊」;若是你的網站是淘寶,用了 Strict 會這樣:某微商在微博上發了條消息「新百倫正品特賣5折起 https://item.taobao.com/item.htm?id=1111111」,結果點進去顧客買不了,也就是說,這種超多用戶的、可能常常須要用戶從別的網站點過來的網站,就不適合用 Strict 了。
假如你的網站有用 iframe 形式嵌在別的網站裏的需求,那麼連 Lax 你也不能用,由於 iframe 請求也是一種異步請求。或者假如別的網站有使用你的網站的 JSONP 接口,那麼一樣 Lax 你也不能用,好比天貓就是經過淘寶的 JSONP 接口來判斷用戶是否登陸的。
有時安全性和靈活性就是矛盾的,須要取捨。
主流瀏覽器都有禁用第三方 cookie 的功能,它和 SameSite 有什麼區別?我能總結 3 點:
1. 該功能是由用戶決定是否開啓的,是針對整個瀏覽器中全部 cookie 的,即使有些瀏覽器能夠設置域名白名單,那最小單位也是域名;而 SameSite 是由網站決定是否開啓的,它針對的是某個網站下的單個 cookie。
2. 該功能同時禁用第三方 cookie 的讀和寫,好比 a.com 發起了對 b.com 的請求,這個請求徹底不會有 Cookie 請求頭,同時假如這個請求的響應頭裏有 Set-Cookie: foo=1,foo 這個 cookie 也不會被寫進瀏覽器裏;而 SameSite 只禁用讀,好比 b.com 在用戶瀏覽器下已經寫入了個 SameSite cookie foo,當 a.com 請求 b.com 時,foo 確定不會被髮送過去,但 b.com 在這個請求的響應裏又返回了: Set-Cookie: bar=1; SameSite=Strcit,這個 bar 會成功寫入瀏覽器的 cookie 裏。
3. 該功能不會把我上面說的那種同步請求(改變了當前頁面或者打開了新頁面)算在第三方請求裏,所以也不會攔截對應的 cookie。
我上面說的原話是:當一個請求自己的 URL 和它的發起頁面的 URL 不屬於同一個站點時,這個請求就算第三方請求。那麼怎樣算是同一個站點?是咱們常常說的同源(same-origin)嗎,cross-origin 的兩個請求就不屬於同一個站點?顯然不是的,foo.a.com 和 bar.a.com 是不一樣源的,但頗有多是同一個站點的,a.com 和 a.com:8000 是不一樣源的,但它倆絕對是屬於同一個站點的,瀏覽器在判斷第三方請求時用的判斷邏輯並非同源策略,而是用了 Public Suffix List 來判斷。
有些同窗可能會這麼想:一個域名能夠用逗號分紅多個字段,若是兩個域名的最後兩個字段都是相同的,那它們就是同一個站點的,好比 foo.a.com 和 bar.a.com 就是。可是 sina.com.cn 和 sohu.com.cn 也知足這個條件啊,它們絕對不是同一個網站吧,那是否是說瀏覽器須要維護一份列表來記錄全部國家頒佈的二級域名啊,可是不只國家能夠開放三級域名給不一樣的網站使用,普通的網站也可能會,好比新浪就開放 *.sinaapp.com 三級域名註冊,foo.sinaapp.com 和 bar.sinaapp.com 是兩個不一樣的網站,那 sinaapp.com 也應該加入那個列表中,以及 github.io 等等。
Mozilla 好久以前就將本身維護的這個域名後綴列表放到了 github 上,起名爲 Public Suffix List,裏面不只有 IANA 頒佈的頂級域名,衆多二級域名,還有三級域名好比 compute.amazonaws.com,甚至四級域名好比 compute.amazonaws.com.cn,判斷兩個 URL 是否是同一個網站的,只要判斷兩個 URL 的域名的 public suffix(按能匹配到的最長的算)以及它前面的那個字段(後面用 public suffix+1 指代)是否都相同,是的話就是同一個站點的,不然不是。好比 www.sina.com.cn 的 public suffix+1 是 sina.com.cn,www.sohu.com.cn 的 public suffix+1 是 sohu.com.cn, 二者不同,因此不屬於同一個站點;再好比 nanzhuang.taobao.com 的 public suffix+1 是 taobao.com,nvzhuang.taobao.com 的 public suffix+1 也是 taobao.com,那麼它倆就是同一個站點的。
Public Suffix List 最初被 Firefox 用在限制 Set-Cookie 響應頭的 Domain 屬性上的, Domain 不能設置成一個比本身網站的 public suffix+1 還高層級的域名,好比 foo.w3c.github.io 就不能設置 Set-Cookie: foo=1; Domain=github.io,最高只能設置成 Set-Cookie: bar=1; Domain=w3c.github.io,如今其它瀏覽器也都在用一樣的列表作一樣的限制。DOM API 裏的 document.domain 後來也加上了這個限制。有些瀏覽器還用這個列表來高亮地址欄上的 URL 中的 public suffix+1 部分(Firefox 和 IE 有用,Chrome 是高亮了整個域名),此外瀏覽器們還用該列表幹一些其它雜事,好比將歷史網址按不一樣站點排列等等。
瀏覽器們會按期同步這份列表,好比 Chrome 是在每一個正式版本發佈以前同步一次。
目前尚未哪一個後臺語言的 API 支持了 SameSite 屬性,好比 php 裏的 setcookie 函數,或者 java 裏的 java.net.HttpCookie 類,若是你想使用 SameSite,須要使用更底層的 API 直接修改 Set-Cookie 響應頭。Node.js 原本就沒有專門設置 cookie 的 API,只有通用的 setHeader 方法,不過 Node.js 的框架 Express 已經支持了 SameSite。
若是以爲開 http 服務測試 SameSite cookie 比較麻煩的話,你也可使用 document.cookie 來代替,好比 document.cookie="foo=1;SameSite=Strict",爲 document.cookie 賦值和使用 Set-Cookie 響應頭的效果幾乎一摸同樣,除了不能讀取和設置帶 HttpOnly 屬性的 cookie 之外。