背景
前幾天在業(yè)務(wù)開發(fā)中,在iframe中嵌入打開一個xxx的url鏈接擒权,在鏈接的主頁中疫稿,會跳轉(zhuǎn)到另一個登錄的頁面恢着,然而登錄一直失敗,失敗原因是xxx的服務(wù)端沒有收到對應(yīng)的cookie危纫。但是在瀏覽器中的頂層搜索打開xxx的url鏈接宗挥,在跳轉(zhuǎn)到另一個登錄的頁面后乌庶,就可以正常的登錄。
頁面嵌套關(guān)系如下所示:
Cookie簡介:
HTTP 協(xié)議是無狀態(tài)的契耿,但可以通過 Cookie 來維持客戶端與服務(wù)端之間的“會話狀態(tài)”瞒大。
簡單來說就是:服務(wù)端通過 Set-Cookie 響應(yīng)頭設(shè)置 Cookie 到客戶端,而客戶端在下次向服務(wù)器發(fā)送請求時添加名為 Cookie 的請求頭搪桂,以攜帶服務(wù)端之前“埋下”的內(nèi)容透敌,從而使得服務(wù)端可以識別客戶端的身份。
場景模擬
本地代碼示例如下:
配置本地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"], // 設(shè)置 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("服務(wù)器啟動成功");
});
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("服務(wù)器啟動成功");
});
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服務(wù)中沒有獲取到cookie信息撵术,所以沒有展示正常的B頁面。
排查問題
那接下來就一塊來分析問題吧话瞧。
既然在瀏覽器頂部導航欄輸入http://b.test.com:3002時可以正常顯示嫩与,那看一下此時的網(wǎng)絡(luò)請求情況:
看一下訪問失敗時的請求頭情況
可以看到請求異常的情況下,請求頭中沒有攜帶cookie信息交排,并且在響應(yīng)頭中會提示SameSite=Lax信息划滋。
查看b站點的Application中的Cookie信息
可以看到本地有b站點的cookie信息。為什么本地有cookie信息埃篓,但是請求的時候request header中沒有攜帶此cookie信息呢处坪?
查找資料得知,從 Chrome 80 開始架专,如果不指定 SameSite 就等效于設(shè)置為 Lax
同窘。
SameSite屬性
SameSite
是 HTTP 響應(yīng)頭 Set-Cookie的屬性之一。它允許聲明該 Cookie 是否僅限于第一方或者同一站點上下文胶征。
SameSite 可以有下面三種值:
- Strict 僅允許一方請求攜帶 Cookie塞椎,即瀏覽器將只發(fā)送相同站點請求的 Cookie,即當前網(wǎng)頁 URL 與請求目標 URL 完全一致睛低。
- Lax 允許部分第三方請求攜帶 Cookie案狠。
- None 無論是否跨站都會發(fā)送 Cookie。
之前默認是 None 的钱雷,Chrome80 后默認是 Lax骂铁。
Lax的情況見下表:
請求類型 | 示例 | 正常情況 | Lax |
---|---|---|---|
鏈接 | <a href="..."></a> |
發(fā)送 Cookie | 發(fā)送 Cookie |
預加載 | <link rel="prerender" href="..."/> |
發(fā)送 Cookie | 發(fā)送 Cookie |
GET 表單 | <form method="GET" action="..."> |
發(fā)送 Cookie | 發(fā)送 Cookie |
POST 表單 | <form method="POST" action="..."> |
發(fā)送 Cookie | 不發(fā)送 |
iframe | <iframe src="..."></iframe> |
發(fā)送 Cookie | 不發(fā)送 |
AJAX | $.get("...") |
發(fā)送 Cookie | 不發(fā)送 |
Image | <img src="..."> |
發(fā)送 Cookie | 不發(fā)送 |
當sameSite為Lax時,post罩抗、iframe拉庵、ajax、image的跨站請求都不會發(fā)送cookie套蒂。
要理解上面的規(guī)則钞支,還需要了解一下跨域和跨站的區(qū)別茫蛹。
跨域和跨站
首先要理解的一點就是跨站和跨域是不同的。同站(same-site)/跨站(cross-site)和第一方(first-party)/第三方(third-party)是等價的烁挟。但是與瀏覽器同源策略(SOP)中的同源(same-origin)/跨域(cross-origin)是完全不同的概念婴洼。
同源策略的同源是指兩個 URL 的協(xié)議/主機名/端口一致。例如撼嗓,https://www.baidu.com柬采,它的協(xié)議是 https,主機名是 www.baidu.com且警,端口是 443粉捻。
同源策略作為瀏覽器的安全基石,其同源判斷是比較嚴格的斑芜。相對而言肩刃,Cookie中的同站判斷就比較寬松:只要兩個 URL 的 eTLD+1 相同即可,不需要考慮協(xié)議和端口押搪。其中树酪,eTLD 表示有效頂級域名浅碾,注冊于 Mozilla 維護的公共后綴列表(Public Suffix List)中大州,例如,.com垂谢、.co.uk厦画、.github.io 等。eTLD+1 則表示滥朱,有效頂級域名+二級域名根暑,例如 baidu.com 等。
舉幾個例子徙邻,www.taobao.com 和 www.baidu.com 是跨站排嫌,a.baidu.com 和 b.baidu.com是同站,a.github.io 和 b.github.io 是跨站(注意是跨站)缰犁。
在上面的模擬示例中我使用的chrome瀏覽器的版本是107版本淳地,雖然本地是有cookie信息,但是SameSite為空帅容,也就是沒有設(shè)置颇象,所以默認SameSite=Lax,導致在A頁面訪問iframe中的B站點時并徘,是跨站的方式遣钳,不會發(fā)送B站點的cookie信息。
解決方案
這種問題的解決方案有以下幾種
1麦乞、服務(wù)器在set-cookie時蕴茴,設(shè)置SameSite=None; Secure劝评。但是這里需要注意:
- HTTP 接口不支持 SameSite=none。如果你想加 SameSite=none 屬性倦淀,那么該 Cookie 就必須同時加上 Secure 屬性付翁,表示只有在 HTTPS 協(xié)議下該 Cookie 才會被發(fā)送。
- 部分瀏覽器不支持部分SameSite=none晃听。IOS 12 的 Safari 以及老版本的一些 Chrome 會把 SameSite=none 識別成 SameSite=Strict百侧,所以服務(wù)端必須在下發(fā) Set-Cookie 響應(yīng)頭時進行 User-Agent 檢測,對這些瀏覽器不下發(fā) SameSite=none 屬性能扒。
2佣渴、使用Nginx或其他網(wǎng)關(guān)工具進行Proxy操作,使跨站請求變?yōu)橥菊埱?/p>
將這個被調(diào)用接口的應(yīng)用和發(fā)起請求的應(yīng)用放在同一個站下面初斑,使他們是同站請求辛润,這樣就不存在跨站問題了。
比如上面模擬示例所示见秤,在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內(nèi)核低于80的瀏覽器围苫,或者在safari中關(guān)閉防止跨站追蹤選項裤园。
以上列出了4種解決此類問題的方法,具體還需要結(jié)合自己的業(yè)務(wù)場景選擇合適的解決方案剂府。
參考:
https://github.com/mqyqingfeng/Blog/issues/157