Skip to main content

Thrid-Party Cookie

· 5 min read

References

介绍

Chrome 从 2024Q1 开始对1%的用户默认开启禁用第三方cookie(Third-Party Cookie). Safari 和 Firefox 已经先于Chrome全面默认禁用第三方 Cookie. 可以通过 chrome://settings/cookies 查看自己浏览器当前设置: setting

禁用第三方cookie之后, 在www.a.com调用(或者引用)b.com域名的接口(或者资源页面等), 是无法设置b.com域名的cookie(即响应头的Set-Cookie是不生效的), 也就是说在 www.a.com 页面只能设置 www.a.com 域名的cookie(包括 xxx.a.com 等任意二级域名).

限制第三方Cookie的初衷是为了限制广告商追踪用户行为, 比如用户访问了 www.a.comwww.b.com, 这两个站点都引用了百度的统计JS, 那百度就可以把用户在这两个站点的行为关联起来, 比如下图, 在未限制第三方Cookie的情况下, 可以看到有第三方的Cookie(.baidu.com.clarity.ms): no-limit

iframe依然受限第三方cookie

www.a.com 页面通过 <iframe src="www.b.com" /> 方式加载 b.com 域名的页面, 在iframe里发起的请求依然无法设置b.com域名的cookie(此场景没有尝试通过 Javascript 方式设置cookie是否可以.).

一级域名相同不受限第三方cookie

经过测试发现,这里的Third-Party指的是一级域名不同(比如 a.comb.com), 如果二级域名相同, 是不算 Third-Party Cookie (比如 www.a.comemail.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

Live Editor

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>);
}
Result
Loading...

设置Cookie

Live Editor
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>);
}
Result
Loading...