今天在做前后端分離項目的時候遇到了這樣一個問題。設置了與跨站點資源http://www.****.com/關聯的cookie,但沒有設置' SameSite '屬性。在未來的Chrome版本中,只有當跨站請求設置為“SameSite=None”和“Secure”時,才會發送cookie。您可以在應用程序>存儲> cookies下查看開發工具中的cookie,並在https://www.chromestatus.com/feature/5088147346030592和https://www.chromestatus.com/feature/5633521622188032查看更多詳細信息。
去查了資料后發現,因為 HTTP 協議是無狀態的,所以很久以前的網站是沒有登錄這個概念的,直到網景發明 cookie 以后,網站才開始利用 cookie 記錄用戶的登錄狀態。cookie 是個好東西,但它很不安全,其中一個原因是因為 cookie 最初被設計成了允許在第三方網站發起的請求中攜帶,CSRF 攻擊就是利用了 cookie 的這一“弱點”,防止 CSRF 攻擊的辦法已經有 CSRF token 校驗和 Referer 請求頭校驗。為了從源頭上解決這個問題,Google 起草了一份草案來改進 HTTP 協議,那就是為 Set-Cookie 響應頭新增 SameSite 屬性,它用來標明這個 cookie 是個“同站 cookie”,同站 cookie 只能作為第一方 cookie,不能作為第三方 cookie。
Chrome 51 開始,瀏覽器的 Cookie 新增加了一個SameSite屬性,用來防止 CSRF 攻擊和用戶追蹤。
SameSite 屬性
Cookie 的SameSite屬性用來限制第三方 Cookie,從而減少安全風險。
它可以設置三個值。
- Strict
- Lax
- None
Strict
Strict最為嚴格,完全禁止第三方 Cookie,跨站點時,任何情況下都不會發送 Cookie。換言之,只有當前網頁的 URL 與請求目標一致,才會帶上 Cookie。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
- 1
這個規則過於嚴格,可能造成非常不好的用戶體驗。比如,當前網頁有一個 GitHub 鏈接,用戶點擊跳轉就不會帶有 GitHub 的 Cookie,跳轉過去總是未登陸狀態。
Lax
Lax規則稍稍放寬,大多數情況也是不發送第三方 Cookie,但是導航到目標網址的 Get 請求除外。
Set-Cookie: CookieName=CookieValue; SameSite=Lax;
- 1
導航到目標網址的 GET 請求,只包括三種情況:鏈接,預加載請求,GET 表單。詳見下表。
請求類型 | 示例 | 正常情況 | Lax |
---|---|---|---|
連接 | <a href="..."></a> |
發送 Cookie | 發送 Cookie |
預加載 | <link rel="prerender" href="..."/> |
發送 Cookie | 發送 Cookie |
GET 表單 | <form method="GET" action="..."> |
發送 Cookie | 發送 Cookie |
POST 表單 | <form method="POST" action="..."> |
發送 Cookie | 不發送 |
iframe | <iframe src="..."></iframe> |
發送 Cookie | 不發送 |
AJAX | $.get("...") |
發送Cookie | 不發送 |
Image | <img src="..."> |
發送Cookie | 不發送 |
設置了Strict或Lax以后,基本就杜絕了 CSRF 攻擊。當然,前提是用戶瀏覽器支持 SameSite 屬性。
None
Chrome 計划將Lax變為默認設置。這時,網站可以選擇顯式關閉SameSite屬性,將其設為None。不過,前提是必須同時設置Secure屬性(Cookie 只能通過 HTTPS 協議發送),否則無效。
下面的設置無效。
Set-Cookie: widget_session=abc123; SameSite=None
- 1
下面的設置有效
Set-Cookie: widget_session=abc123; SameSite=None; Secure
- 1
該用哪種模式?
該用哪種模式,要看你的需求。比如你的網站是一個少數人使用的后台管理系統,所有人的操作方式都是從自己瀏覽器的收藏夾里打開網址,那我看用 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”功能有什么區別?
主流瀏覽器都有禁用第三方 cookie 的功能,它和 SameSite 有什么區別?我能總結 2 點:
-
該功能是由用戶決定是否開啟的,是針對整個瀏覽器中所有 cookie 的,即便有些瀏覽器可以設置域名白名單,那最小單位也是域名;而 SameSite 是由網站決定是否開啟的,它針對的是某個網站下的單個 cookie。
-
該功能同時禁用第三方 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 里。
怎樣才算第三方請求?
當一個請求本身的 URL 和它的發起頁面的 URL 不屬於同一個站點時,這個請求就算第三方請求。那么怎樣算是同一個站點?是我們經常說的同源(same-origin)嗎,cross-origin 的兩個請求就不屬於同一個站點?顯然不是的,foo.a.com 和 bar.a.com 是不同源的,但很有可能是同一個站點的,a.com 和 a.com:8000 是不同源的,但它倆絕對是屬於同一個站點的,瀏覽器在判斷第三方請求時用的判斷邏輯並不是同源策略,而是用了 Public Suffix List 來判斷。
后台語言的支持程度
目前還沒有哪個后台語言的 API 支持了 SameSite 屬性,比如 php 里的 setcookie 函數,或者 java 里的 java.net.HttpCookie 類,如果你想使用 SameSite,需要使用更底層的 API 直接修改 Set-Cookie 響應頭。Node.js 本來就沒有專門設置 cookie 的 API,只有通用的 setHeader 方法,不過 Node.js 的框架 Express 已經支持了 SameSite。
參考網址 https://blog.csdn.net/yanyang1116/article/details/56511009,
https://www.jianshu.com/p/66f77b8f1759,
https://www.cnblogs.com/ziyunfei/p/5637945.html,
http://www.ruanyifeng.com/blog/2019/09/cookie-samesite.html。