Thrid-Party Cookie
References
介绍
Chrome 从 2024Q1 开始对1%的用户默认开启禁用第三方cookie(Third-Party Cookie). Safari 和 Firefox 已经先于Chrome全面默认禁用第三方 Cookie. 可以通过 chrome://settings/cookies 查看自己浏览器当前设置:
禁用第三方cookie之后, 在www.a.com
调用(或者引用)b.com
域名的接口(或者资源页面等), 是无法设置b.com
域名的cookie(即响应头的Set-Cookie
是不生效的), 也就是说在 www.a.com
页面只能设置 www.a.com
域名的cookie(包括 xxx.a.com
等任意二级域名).
限制第三方Cookie的初衷是为了限制广告商追踪用户行为, 比如用户访问了 www.a.com
和 www.b.com
, 这两个站点都引用了百度的统计JS, 那百度就可以把用户在这两个站点的行为关联起来, 比如下图, 在未限制第三方Cookie的情况下, 可以看到有第三方的Cookie(.baidu.com
和.clarity.ms
):
iframe依然受限第三方cookie
在 www.a.com
页面通过 <iframe src="www.b.com" />
方式加载 b.com
域名的页面, 在iframe里发起的请求依然无法设置b.com
域名的cookie(此场景没有尝试通过 Javascript 方式设置cookie是否可以.).
一级域名相同不受限第三方cookie
经过测试发现,这里的Third-Party指的是一级域名不同(比如 a.com
和 b.com
), 如果二级域名相同, 是不算 Third-Party Cookie (比如 www.a.com
和 email.a.com
). 也就是说 www.a.com
调用接口 email.a.com/api/unread
, 虽然算是跨域请求, 但是响应头 Set-Cookie
是被允许的.
关于SSO登录
如果是302或者301跳转页面之后设置cookie(Set-Cookie
)是可以的, 用户访问www.a.com
页面, 检测用户未登录, 从www.a.com
跳转到www.sso.com?backurl=www.a.com
, 在 sso.com
登录完成后(这一步www.sso.com
是可以写sso.com
域名的cookie的)跳转到 www.a.com/auth?ticket=xxx
, 然后www.a.com/auth
写入a.com
域名的cookie实现登录.
测试
下面是关于浏览器禁用第三方Cookie测试代码:
加载iframe
function render(){ const [loadIframe, setLoadIframe] = useState(false); const domain = new URLSearchParams(location.search).get("domain") || "fe.iodv.cn"; const url = `https://${domain}/PrintHeaders`; return (<div> <h3>是否加载 iframe <input type="checkbox" onChange={e => setLoadIframe(e.target.checked)} checked={loadIframe} /> </h3> { loadIframe && <iframe src={url} width="100%" height="500px" frameBorder="0" scrolling="auto"></iframe> } </div>); }
设置Cookie
function render(){ const domain = new URLSearchParams(location.search).get("domain") || "fe.iodv.cn"; const [api, setApi] = useState(`https://${domain}/api/cookie`); const [httpMethod, setHttpMethod] = useState("GET"); const [httpResult, setHttpResult] = useState(""); const [sendCookies, setSendCookies] = useState(""); const [cookieInfo, setCookieInfo] = useState({ name: "hello", value: "world", Domain: domain, HttpOnly: false, // Expires: new Date(Date.now() + (300 * 1000)).toUTCString(), // 300s 后过期 "Max-Age": 1200, Path: "/", SameSite: "Lax", Secure: false }); useEffect(() => { console.log("cookieInfo: ", JSON.stringify(cookieInfo)); const cookies = Object.keys(cookieInfo) .map(key => ({ key: key, value: cookieInfo[key] })) .reduce((p, n) => { if(!n.value){ return p; } if(httpMethod === "GET"){ p += `${n.key}=${encodeURIComponent(n.value)}&`; } else { p[n.key] = n.value; } return p; }, httpMethod === "GET" ? "" : {}); setSendCookies(typeof cookies === "string" ? cookies : JSON.stringify(cookies)); }, [cookieInfo]); const list = Object.keys(cookieInfo) .map(key => ({ key: key, value: cookieInfo[key], })) .map(kv => ({ ...kv, render: () => { const t = typeof kv.value; if(t === "boolean") { return <input type="checkbox" checked={kv.value} onChange={e => setCookieInfo({ ...cookieInfo, [kv.key]: e.target.checked })} /> } return <input type={t === "number" ? "number" : "text"} value={kv.value} onChange={e => setCookieInfo({ ...cookieInfo, [kv.key]: e.target.value })} /> } })); const send = async () => { if(httpMethod !== "GET" && httpMethod !== "POST") { setHttpResult(`请求方法必须是 GET 或者 POST (区分大小写).`); return; } const origin = "origin=" + encodeURIComponent(location.origin); const query = httpMethod === "GET" ? sendCookies + "&" + origin : origin; const response = await fetch([api, query].join("?"), { method: httpMethod, headers: { "Content-Type": "application/json", }, credentials: "include", body: httpMethod === "POST" ? sendCookies : undefined }); const json = await response.json(); setHttpResult(JSON.stringify(json, null, " ")); }; return (<div> <h3>Cookie信息: </h3> { list.map(item => (<div key={item.key}> <label>{item.key}: { item.render() } </label> </div>)) } <hr /> <h3>请求信息</h3> <div> <label>接口地址: <input type="text" value={api} onChange={e => setApi(e.target.value)} /> </label> </div> <div> <label>请求方法: <input type="text" value={httpMethod} onChange={e => setHttpMethod(e.target.value)} /> </label> </div> <div> <div>待传递Cookie值:</div> <div>{sendCookies}</div> </div> <div> <button onClick={send}>发起请求</button> </div> <hr /> <div> 返回结果: <br /> <pre>{httpResult}</pre> </div> </div>); }