聊聊Cookie的SameSite屬性

背景

前幾天在業務開發中,在iframe中嵌入打開一個xxx的url鏈接,在鏈接的主頁中,會跳轉到另一個登錄的頁面,然而登錄一直失敗,失敗原因是xxx的服務端沒有收到對應的cookie。但是在瀏覽器中的頂層搜索打開xxx的url鏈接,在跳轉到另一個登錄的頁面後,就可以正常的登錄。

頁面嵌套關係如下所示:

Cookie簡介:

HTTP 協議是無狀態的,但可以通過 Cookie 來維持客戶端與服務端之間的“會話狀態”。

簡單來說就是:服務端通過 Set-Cookie 響應頭設置 Cookie 到客戶端,而客戶端在下次向服務器發送請求時添加名爲 Cookie 的請求頭,以攜帶服務端之前“埋下”的內容,從而使得服務端可以識別客戶端的身份。

場景模擬

本地代碼示例如下:

配置本地host

127.0.0.1 a.cross.com
127.0.0.1 b.test.com
127.0.0.1 a.test.com

開啓serverB:

const http = require("http");
const fs = require("fs");

let server = http.createServer((req, res) => {
    const cookie = req.headers.cookie
    console.log('cookie', cookie);
    res.writeHead(200, [
        ["Set-Cookie", "name=bbb"], // 設置 cookie
    ]);

    if (!cookie) {
        res.end("no cookie");//沒有cookie時
        return
    }
    if ("/" == req.url) {
        fs.readFile(__dirname + "/index.html", "utf-8", (err, data) => {
            if (err) {
                throw err;
            } else {
                res.end(data);
            }
        });
    } else if (req.url == "/favicon.ico") {
        res.statusCode = 204;
        res.end();
    } else {
        res.end("404 NOT Found");
    }

});
server.listen(3002, () => {
    console.log("服務器啓動成功");
});

serverB中index.html的body爲:

<body>
    <div>i am B頁面</div>
</body>

在瀏覽器頂部導航欄輸入http://b.test.com:3002時,正常的B頁面展示爲

在A中使用iframe嵌套B的url,開啓serverA:

const http = require("http");
const fs = require("fs");

let server = http.createServer((req, res) => {
    console.log(req.url);
    if ("/" == req.url) {
        fs.readFile(__dirname + "/index.html", "utf-8", (err, data) => {
            if (err) {
                throw err;
            } else {
                res.end(data);
            }
        });
    } else if (req.url == "/favicon.ico") {
        res.statusCode = 204;
        res.end();
    } else {
        res.end("404 NOT Found");
    }

});
server.listen(3001, () => {
    console.log("服務器啓動成功");
});

serverA中index.html的body爲:

<body>
    <div>i am A頁面</div>
    <iframe src="http://b.test.com:3002"></iframe>//iframe嵌套B頁面的url
</body>

在chrome中打開http://a.cross.com:3001,頁面展示如下:

可以看到在A頁面的iframe中嵌套B頁面時,在B服務中沒有獲取到cookie信息,所以沒有展示正常的B頁面。

排查問題

那接下來就一塊來分析問題吧。

既然在瀏覽器頂部導航欄輸入http://b.test.com:3002時可以正常顯示,那看一下此時的網絡請求情況:

看一下訪問失敗時的請求頭情況

可以看到請求異常的情況下,請求頭中沒有攜帶cookie信息,並且在響應頭中會提示SameSite=Lax信息。

查看b站點的Application中的Cookie信息

可以看到本地有b站點的cookie信息。爲什麼本地有cookie信息,但是請求的時候request header中沒有攜帶此cookie信息呢?

查找資料得知,從 Chrome 80 開始,如果不指定 SameSite 就等效於設置爲 Lax

SameSite屬性

SameSite 是 HTTP 響應頭 Set-Cookie的屬性之一。它允許聲明該 Cookie 是否僅限於第一方或者同一站點上下文。

SameSite 可以有下面三種值:

  1. Strict 僅允許一方請求攜帶 Cookie,即瀏覽器將只發送相同站點請求的 Cookie,即當前網頁 URL 與請求目標 URL 完全一致。
  2. Lax 允許部分第三方請求攜帶 Cookie。
  3. None 無論是否跨站都會發送 Cookie。

之前默認是 None 的,Chrome80 後默認是 Lax。

Lax的情況見下表:

請求類型 示例 正常情況 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 不發送

當sameSite爲Lax時,post、iframe、ajax、image的跨站請求都不會發送cookie。

要理解上面的規則,還需要了解一下跨域和跨站的區別。

跨域和跨站

首先要理解的一點就是跨站和跨域是不同的。同站(same-site)/跨站(cross-site)和第一方(first-party)/第三方(third-party)是等價的。但是與瀏覽器同源策略(SOP)中的同源(same-origin)/跨域(cross-origin)是完全不同的概念。

同源策略的同源是指兩個 URL 的協議/主機名/端口一致。例如,https://www.baidu.com,它的協議是 https,主機名是 www.baidu.com,端口是 443。

同源策略作爲瀏覽器的安全基石,其同源判斷是比較嚴格的。相對而言,Cookie中的同站判斷就比較寬鬆:只要兩個 URL 的 eTLD+1 相同即可,不需要考慮協議和端口。其中,eTLD 表示有效頂級域名,註冊於 Mozilla 維護的公共後綴列表(Public Suffix List)中,例如,.com、.co.uk、.github.io 等。eTLD+1 則表示,有效頂級域名+二級域名,例如 baidu.com 等。

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

在上面的模擬示例中我使用的chrome瀏覽器的版本是107版本,雖然本地是有cookie信息,但是SameSite爲空,也就是沒有設置,所以默認SameSite=Lax,導致在A頁面訪問iframe中的B站點時,是跨站的方式,不會發送B站點的cookie信息。

解決方案

這種問題的解決方案有以下幾種

1、服務器在set-cookie時,設置SameSite=None; Secure。但是這裏需要注意:

  • HTTP 接口不支持 SameSite=none。如果你想加 SameSite=none 屬性,那麼該 Cookie 就必須同時加上 Secure 屬性,表示只有在 HTTPS 協議下該 Cookie 纔會被髮送。
  • 部分瀏覽器不支持部分SameSite=none。IOS 12 的 Safari 以及老版本的一些 Chrome 會把 SameSite=none 識別成 SameSite=Strict,所以服務端必須在下發 Set-Cookie 響應頭時進行 User-Agent 檢測,對這些瀏覽器不下發 SameSite=none 屬性。

2、使用Nginx或其他網關工具進行Proxy操作,使跨站請求變爲同站請求

將這個被調用接口的應用和發起請求的應用放在同一個站下面,使他們是同站請求,這樣就不存在跨站問題了。

比如上面模擬示例所示,在host配置中,將A站點127.0.0.1使用a.test.com映射,這樣在a.test.com中訪問b.test.com就是同站訪問了。

或者使用nginx代理請求,將a.test.com代理到a.cross.com,這樣在瀏覽器中頂部導航欄中輸入a.test.com就可以被nginx代理訪問到a.cross.com,而這時瀏覽器會認爲在a.test.com頁面中訪問b.test.com,瀏覽器會當作同站處理cookie。同理可以使用nginx代理b站點的url,使與A站點同站。

3、使用http auth也就是header auth方式進行,將令牌通過header的形式傳輸,不使用Cookie,那當然也就不存在Cookie中奇奇怪怪的問題了。

4、使用指定版本的瀏覽器,使用chrome內核低於80的瀏覽器,或者在safari中關閉防止跨站追蹤選項。

以上列出了4種解決此類問題的方法,具體還需要結合自己的業務場景選擇合適的解決方案。

參考:

https://github.com/mqyqingfeng/Blog/issues/157

https://zhuanlan.zhihu.com/p/266282015

https://juejin.cn/post/6963632513914765320#heading-6

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章