Cook Cookie, 我把 SameSite 給你燉爛了

Denzel發表於2020-12-30

什麼樣的知識,最好吃?入口即化的

囉裡吧嗦的開頭

SameSite cookie 推出已一年有餘,自己看了不少文章,也撞了不少南牆,所以還是那句好記性不如爛筆頭。你可能覺得自己懂了,但試著講出來,才能知道自己是否真的懂了。

列一列自己看過的文章:

走進SameSite

和新冠一起火了的SameSite

SameSite Cookie行為更新去年就開始被提上日程,2020年2月隨著Chrome 80的推出,這個屬性開始正式生效,但因為3月份正是全球新冠肆虐的時候,當時的維護者都還活在新冠的恐懼之下,網站沒法及時更新,這項政策導致很多網站癱瘓,chrome官方又在4月進行了回滾,暫時終止了這項策略。直到2020年7月14日Chrome 84穩定版開始,重新恢復SameSite cookie策略,並且會逐步部署到Chrome 80以及以上的版本中。

如果你知道CSRF,那你就知道這項攻擊真正的核心就是利用 cookie 自動攜帶的行為(進一步講就是跨站攜帶cookie)。

之所以會跨站攜帶,是因為起初 cookie 的規範中並沒有 SameSite 這個屬性;直到2016年first-party-cookies草案的推出,但並有多少人真正去用,而瀏覽器這邊的實現也預設是SameSite=None,所以對開發者並沒有什麼影響,自然就沒有引起多大的關注,至少不如這次,而提案初衷:改善安全和隱私洩露的問題。效果自然就不是很理想。

為了解決這些問題,一個新的草案:Incrementally Better Cookies,在草案的開頭,就直接說道:

This document proposes two changes to cookies inspired by the properties of the HTTP State Tokens mechanism proposed in [I-D.west-http-state-tokens]. First, cookies should be treated as "SameSite=Lax" by default. Second, cookies that explicitly assert "SameSite=None" in order to enable cross-site delivery should also be marked as "Secure"

翻譯成中文就是兩個改進

  1. "SameSite=Lax" 變成預設設定,取代現在的"SameSite=None";
  2. 如果硬要設定成"SameSite=None",則需要同時增加"Secure"標識,即這個cookie只能在Https連結上傳輸;

first-party-cookies 草案規定了什麼

草案那麼長,我就關(kan)注(dong)了一段話:
20201208223403

可以簡單理解為同站 和 跨站的定義,中文大白話翻譯就是:

  1. 如果請求的url是空,則瀏覽器需要以domain為請求的地址;比如 closertb.site 網站html有這樣一個<script src="/static/index.js"></script> 標籤,那最後瀏覽器發出的請求就是:closertb.site/static/index.js,這沒得說,肯定是同站;
  2. 如果請求domain 和 瀏覽器位址列url匹配(注意是匹配,不是相等),也是同站;
  3. 除此以外,都是跨站;

好傢伙,感覺說了和沒說一樣;然後定義了SameSite語義:

“ SameSite”限制了cookie的使用範圍,以便它 僅在這些請求是“相同站點”時附加到請求中, 由2.1節中的演算法定義。例如,要求 對於“https://example.com/sekrit-image”, 將附加相同站點的cookie 即當且僅當從其站點為“example.com”。

但在接下來的幾節,並沒有出現StrictLaxNone 這些語義,我猜主要還是這只是一份草稿,還不涉及到具體實現。

在最新的RFC6265 替代草案draft-ietf-httpbis-rfc6265bis-05, 提及了這三個屬性值,並做了介紹,但貌似還是落後現在瀏覽器的實現,因為草案中SameSite=None仍然是預設屬性;

SameSite 的屬性值及其區別

我覺得這部分再講就是蛋炒飯了,畢竟今年太多人講過了,沒啥意義。

你可以看:阮一峰老師

但更推薦MDN官方的:SameSite cookies

你可以通過:https://samesite-sandbox.glit... 網站來了解你的瀏覽器是否開啟Incrementally Better Cookies(IBC)

20201214224921

用例項說話,到底限制的是什麼?

直到現在,其實很多前端開發者對這個變化是無感的,主要兩個原因:

  • 鑑權token化,cookie更多充當儲存;
  • 業務太簡單,cookie使用的場景都是同站的,所以新規並沒有多大影響,新規是針對跨站做cookie攜帶限制的;

同站的影響是不大的

大多數公司cookie都是用來做單點登入身份鑑權的,這種多數存在同站;比如:

  • 鑑權服務:sso.closertb.site
  • erp服務:erp.closertb.site
  • crm服務:crm.closertb.site
  • 其他服務:xxx.closertb.site

當使用者初次開啟crm服務網站時,會首先呼叫鑑權服務查詢該使用者是否已授權或授權有效?如果無效,就會重定向到sso.closertb.site網站讓使用者登入授權,授權完成後,服務會在closertb.site種下幾個cookie,用於識別使用者身份,然後就重定向回crm.closertb.site網站;當使用者再次開啟crm服務或其他同站網站時,也會先呼叫鑑權服務,由於cookie還有效,所以使用者就不用再去登入了,可以繼續在網站瀏覽。

20201227233737

以上就是一個非常典型的單點登入設計,都是利用瀏覽器在同站請求會自動攜帶cookie來做身份識別的方式;

跨站才是重點

但還有一種稍微複雜的情況,跨站單點登入,舉個典型的例子:天貓和淘寶

20201228003050

這個情況複雜就複雜在淘寶和天貓是跨站的,當淘寶登入了時,你再去訪問天貓,會有一系列的鑑權操作:

20201228003949

但今天不是主講淘寶天貓的單點登入設計,主要是講cookie,這裡有一個設計,就是跨站cookie的使用;在使用者登入了淘寶後,使用者再去開啟天貓,因為天貓淘寶是一套使用者體系,所以他們做了免登,就是截圖所示,先是呼叫了了top-tmm.taobao.com/login_api.do, 這個請求會攜帶*.taobao.com域下的cookie, 從而服務端就能知道這個使用者登入過。由於請求是在 tmall.com 站點下發出的,所以這是一個跨站請求,這也是這次新規重點照顧的場景。

為了在新版本瀏覽器下,能繼續讓單點登入有效,所以淘寶的開發也就做點改變來適應, cookie 都打上了samesite=None與secure標識, 利用改進第二條規則。

20201228215342

由於Secure的限制,要攜帶的跨站點請求必須在帶有安全標識站點下發出請求

所以當你輸入http://tmall.com,站點302必會重定向到https://tmall.com 安全域名下。

除了上面這種故意設計的跨站,還有很多其他形式,已經有高手總結過,我就發一個我個人覺得比較全的,來自知乎

20201228230504

對開發的影響

雖然我們大多數開發開發的站點都是同站,但在本地除錯時,基本都是在http://localhost:port站點下進行,要拿到登陸態,面臨的情況都是跨站。所以為了應對這次更新,一般有兩種方法來解決:修改安全限制修改除錯站點域名

修改安全限制就像
關閉跨域限制一樣,只需要開啟chrome://flag站點進行設定,如下圖所示:
20201228223230

上面那種改動粗暴,雖然有效,但不適合我們文化人,文化人就得乾點文化人該乾的事,修改除錯站點地址, 比如:

  • 本地server 站點地址 localhost:8906 切換到local.closertb.site,一般來說都需要修改webpack host地址;
  • 給local.closertb.site加本地解析,說人話,就是修改host解析
  • 瀏覽器訪問,看看效果

一些前端要懂的服務端知識

同源 VS 同站 或 跨域 VS 跨站

同源,基本上懂行的前端都知道,只有請求的地址與站點是同協議、同域名、通埠,才能稱為同源,除此以外,都是跨域;

而同站就沒這麼嚴格,簡單看看同站定義:只要兩個 URL 的 eTLD + 1 相同即可,不需要考慮協議和埠。

其中,eTLD 表示有效頂級域名,註冊於 Mozilla 維護的公共字尾列表(Public Suffix List)中,例如,.com、.co.uk、.github.io 等。eTLD+1 則表示,有效頂級域名+二級域名,例如taobao.com等。

舉幾個例子,www.taobao.com和www.baidu.com是跨站,www.a.taobao.com和www.b.taobao.com是同站,a.github.io和b.github.io是跨站(注意是跨站)。

為什麼a.github.io和b.github.io是跨站,因為 github.io 是有效頂級域名

再來些零碎的

1.雖然同站可以攜帶cookie,但跨了域的同站請求不會主動攜帶,fetch需要設定credentials屬性為include(ajax有相似設定), 但這只是開始,因為設定了這個屬性攜帶了cookie後,這個請求就變成了非簡單請求,服務端需要針對請求的站點設定Access-control-Allow-Credentials,如圖下;
20201203215510
20201216220316
20201203212417
2.服務端 set-cookie 也需要遵循同站規則,否則不生效;eg: 當前網站域名local.closertb.site, 登入傳送請求/user/login,但該請求向admin.closertb.site種了一個cookie, 這個玩法就是非法的(管太寬),這個請求僅可種domai為'local.closertb.site' 或 'closertb.site';為無效時,瀏覽器會標黃提醒
20201216222016

3.cookie 的path 是針對於請求地址的,和當時瀏覽器地址無關;path 常用於多個服務通過一個閘道器來給前端提供介面,為儘量區分各個服務的cookie,所以有這個path屬性設定,這樣可以減少請求攜帶的cookie數量;圖下所示的cookie,就只會在請求地址是以/rule 開頭時才攜帶,其他地址就會忽略;
20201203212610
20201203212417

4.ajax 和 fetch 請求,響應302, 是不能直接改變瀏覽器地址進行跳轉的,除非前端手動去操作;這就是為什麼我們會說ajax 和 fetch 是前後端分離的開始,因為以前很多jsp 或 php頁面,如果使用者沒有許可權,就302重定向到登入頁;而前後端分離後,更常見的做法是響應401,然後前端再手動跳轉到登入頁;

相關文章