bigywater.tech
网络协议 · 81

Cookie:Set-Cookie 属性、作用域与 SameSite

Cookie 是 HTTP 里最古老、也最容易误用的状态机制:一个 Set-Cookie 头里能写多少属性,它们怎么决定一个 Cookie 在什么时候被保存、什么时候被带上?这篇看真实站点的 Set-Cookie,用 cookiejar 验证保存与发送的规则,最后用规则模拟表整理 SameSite 的三种取值。

4 个
baidu.com 首页响应里的 Set-Cookie 头数量(同名头可重复出现)
5 / 8
服务器下发的 8 个 Cookie 里 cookiejar 实际保存的个数
68 年
baidu.com 长期标识 Cookie 的 Max-Age(2147483647 秒)

Set-Cookie 的样子

实测 · 本机执行输出
  收到 4 个 Set-Cookie 头(同名头可以出现多次,不能像别的头一样用逗号合并)
    名字 BAIDUID        值 E9C938…(37 字符);属性 {'expires': 'Thu, 31-Dec-37 23:55:55 GMT', 'max-age': '2147483647', 'path': '/', 'domain': '.baidu.com'}
    名字 BIDUPSID       值 E9C938…(32 字符);属性 {'expires': 'Thu, 31-Dec-37 23:55:55 GMT', 'max-age': '2147483647', 'path': '/', 'domain': '.baidu.com'}
    名字 PSTM           值 179206…(10 字符);属性 {'expires': 'Thu, 31-Dec-37 23:55:55 GMT', 'max-age': '2147483647', 'path': '/', 'domain': '.baidu.com'}
    名字 BAIDUID        值 E9C938…(37 字符);属性 {'max-age': '31536000', 'expires': 'Fri, 15-Oct-27 10:28:49 GMT', 'domain': '.baidu.com', 'path': '/', 'version': '1', 'comment': 'bd'}
  带 Max-Age 的:[](秒;2147483647 秒 ≈ 68 年)

名字=值加一串属性;同名头可重复出现,不能逗号合并。

保存与发送规则

实测 · 本机执行输出
  服务器下发了 8 个 Cookie(a~h);cookiejar 实际保存了 5 个:
    a=1  domain=127.0.0.1  path=/  secure=False  过期=会话结束  仅HTTP=False
    c=3  domain=127.0.0.1  path=/  secure=True  过期=会话结束  仅HTTP=False
    d=4  domain=127.0.0.1  path=/  secure=False  过期=1792063729  仅HTTP=True
    b=2  domain=127.0.0.1  path=/admin  secure=False  过期=会话结束  仅HTTP=False
    h=8  domain=127.0.0.1  path=/api  secure=False  过期=会话结束  仅HTTP=False
  没有保存的:['e', 'f', 'g'](e:Domain=example.org 与请求的主机不匹配,被拒绝;f:Max-Age=0 表示立刻删除;g:Expires 已过期)
  请求 /         → 带上的 Cookie 头:a=1; d=4
  请求 /admin/x  → 带上的 Cookie 头:b=2; a=1; d=4
  请求 /api/x    → 带上的 Cookie 头:h=8; a=1; d=4
  c(Secure)在明文 HTTP 请求里没有被带上;b(Path=/admin)只在 /admin 下带上;h(Path=/api)在 /api 下带上;SameSite 是浏览器对“跨站请求”的限制,cookiejar 不实现,所以 h 的 SameSite=Strict 在这里没有起作用

域名不匹配被拒绝,Max-Age=0 删除,已过期丢弃;请求带上哪些由 Path、Secure、Domain 决定,长路径排前。

大小

实测 · 本机执行输出
  Cookie 头值长   100 字节 → HTTP/1.1 200 OK
  Cookie 头值长  4000 字节 → HTTP/1.1 200 OK
  Cookie 头值长  8000 字节 → HTTP/1.1 200 OK
  Cookie 头值长 16000 字节 → HTTP/1.1 200 OK
  Cookie 头值长 32000 字节 → HTTP/1.1 200 OK
  Cookie 头值长 64000 字节 → HTTP/1.1 200 OK

example.com 到 64000 字节的 Cookie 头都接受。

SameSite

实测 · 本机执行输出
    情形                                             Strict   Lax    None(须 Secure)
    同站点内的任何请求                                        带      带      带   
    在 b.com 页面里的 <img src=a.com/x>                   不带     不带     带   
    在 b.com 页面里点链接跳到 a.com(顶层导航 GET)                 不带     带      带   
    b.com 的表单 POST 到 a.com(顶层导航 POST)                不带     不带     带   
    b.com 的 fetch/XHR 请求 a.com                       不带     不带     带   
  (表中“站点”指可注册域名 eTLD+1,example.com 与 api.example.com 算同一站点;这张表是我按规范写的,不是实测浏览器)

Strict 只在同站点带;Lax 额外允许顶层导航的 GET;None 总是带但必须 Secure。表是规则模拟。

分步演示

Cookie
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64(Apple 芯片)/ Python 3.14.8 / tcpdump(libpcap)/ scapy 2.8 / dpkt / dnspython / h2 / aioquic / OpenSSL / curl 8.7.1;抓包在 lo0(环回)上做,少数实验用真实网卡 en0(这台机器上 en0 是 Wi-Fi,tcpdump 看到的是系统转成以太网格式的帧,所以文中的“以太网头”都指这个 14 字节的头)或访问公网(会在文章里注明);需要丢包 / 延迟 / 限速时,用一个只作用于 lo0 端口 9400-9499 的 dummynet 辅助脚本(netlab);所有服务端都是本机自己起的进程;时间戳取自 tcpdump 的 -tt,数字每次运行会有小幅抖动。第一节是对 www.baidu.com 的一次 HTTPS 请求(Cookie 值只留前 6 个字符);第二节用本机脚本服务器和 Python 的 http.cookiejar;第三节是对 example.com:80 的明文请求;第四节是按规范写的判断函数,没有用真实浏览器
    • 没有验证:真实浏览器的 Cookie 存储上限、清理策略与第三方 Cookie 限制;__Host- / __Secure- 前缀;Partitioned(CHIPS)Cookie;Set-Cookie 的 Expires 与 Max-Age 同时出现时的优先级(规范是 Max-Age 优先);Cookie 属性在 IPv6 / IP 地址主机上的域匹配;HTTP/2 对 Cookie 头的压缩
    京ICP备2026067502号-1京公网安备11010502063487号