# 分区 Cookie（CHIPS）

> Set-Cookie 的 Partitioned 属性如何让被嵌入站点在每个顶级站点下拥有独立 Cookie 罐、Secure 与 __Host- 规则、Chrome 的限制与各引擎支持。

Cookies Having Independent Partitioned State（CHIPS）让服务端通过 `Set-Cookie` 的 `Partitioned` 属性把某个 Cookie 选入分区存储：被嵌入的第三方在每个嵌入它的顶级站点下各有一个独立的 Cookie 罐。嵌入的聊天挂件或地图在某个站点上照常工作，但不再能充当跨站标识符，因为在 `retail.example` 下写入的罐子在 `news.example` 下读不到。

## 工作原理

分区 Cookie 是双键的。浏览器用「设置它的主机」和「分区键」这一对来存储它；分区键是设置该 Cookie 的那次请求开始时浏览器正在显示的顶级页面的站点（scheme 加可注册域名）。`support.chat.example` 在嵌入于 `https://retail.example` 时设置的 Cookie 以 `{("support.chat.example"), ("https", "retail.example")}` 为键；同一个嵌入内容在另一个站点上得到的是空罐子，并在那里设置自己的 Cookie（[Cookies Having Independent Partitioned State](https://privacysandbox.google.com/cookies/chips)，privacysandbox.google.com）。

### 属性要求

该属性只在与 `Secure` 同时出现时被接受，因此从构造上就是仅限 HTTPS；推荐加 `__Host-` 前缀，让 Cookie 绑定到单个主机名而不是可注册域名。`SameSite=None` 才是允许 Cookie 在第三方上下文中被发送的那个属性，没有它，无论是否分区，嵌入请求都不会带上该 Cookie。

```http
Set-Cookie: __Host-session=5f2b; Secure; Path=/; SameSite=None; Partitioned;
```

省略 `Partitioned` 的 Cookie 是未分区的，适用浏览器对第三方 Cookie 的既有策略：Firefox 严格模式和 Safari 默认拦截，Chrome 取决于用户设置。

### 限制与分区

Chrome 每个分区最多存 180 条 Cookie，且每个被嵌入站点的分区上限为 10 KB；在一个繁忙的顶级站点上设置许多小 Cookie 的挂件会先撞到条数上限。每个顶级站点是一个独立分区，同一个挂件被一千个站点嵌入就持有一千个罐子，各自独立受限。

### 与 Firefox 状态分区的关系

Firefox 在增强型跟踪保护的严格模式和隐私窗口里按顶级站点分区所有第三方 Cookie 和存储 API，不需要任何属性，并且对网络状态（HTTP 缓存、DNS、连接池）做永久分区（[State Partitioning](https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/State_Partitioning)，developer.mozilla.org）。CHIPS 是在各引擎中行为一致的选入机制，这正是即使存在默认分区，该属性仍是可移植选择的原因。

### 支持情况

该属性在 Chrome 100 至 116 的 origin trial 之后于 Chrome 114 发布，Firefox 141 与 Safari 26.2 跟进（BCD `http.headers.Set-Cookie.Partitioned`）。不认识 `Partitioned` 的引擎会忽略该属性并把 Cookie 按未分区存储，这正是 `Secure` 和 `SameSite=None` 要求的意义所在：它们让回退后的 Cookie 仍然可用。

## 示例

一个示例从被嵌入的服务端设置 Cookie，另一个在浏览器允许的范围内从脚本检查分区状态。

### 从被嵌入的服务设置分区会话 Cookie

被嵌入源的服务端在对 iframe 第一次请求的响应上设置该 Cookie。嵌入页面无需任何改动。

```js
// support.chat.example 上的 Express 处理函数
app.get('/widget/session', (req, res) => {
  res.setHeader(
    'Set-Cookie',
    '__Host-chat=9b1f; Secure; Path=/; SameSite=None; Partitioned; Max-Age=86400'
  );
  res.json({ ok: true });
});
```

同一个 iframe 在同一个顶级站点上的下一次请求会带上该 Cookie；在另一个顶级站点上服务端看不到 Cookie 并开启新会话，这正是预期行为。

### 通过 Cookie Store API 读取分区状态

`document.cookie` 只返回 `name=value` 对，脚本无法借它区分分区与未分区的 Cookie。Cookie Store API（Chrome 87、Firefox 140、Safari 18.4）在每条 Cookie 上暴露 `partitioned` 布尔值。

```js
async function partitionedCookies() {
  if (!('cookieStore' in window)) {
    return null; // 没有 Cookie Store API：脚本观察不到分区状态
  }
  const cookies = await window.cookieStore.getAll();
  return cookies.filter((cookie) => cookie.partitioned);
}
```

返回 `null` 就该转而依赖服务端视角：服务端可以记录 `__Host-` Cookie 是否到达，以及对应的 `Sec-Fetch-Site` 值。

:::observed
Chrome DevTools 的 Application > Storage > Cookies 有一列 **Partition Key**：带 `Partitioned` 设置的 Cookie 在此列显示顶级站点（例如 `https://retail.example`），其他 Cookie 该列为空（[View, edit, and delete cookies](https://developer.chrome.com/docs/devtools/application/cookies)，developer.chrome.com）。同一 Cookie 不带 `Secure` 再设置一次，此列为空、Cookie 未分区，Console 里没有任何警告；Chrome 报告被拒绝的 `Set-Cookie` 的地方是 Issues 面板。
:::

## 另请参阅

- [Cookies Having Independent Partitioned State (CHIPS)](https://privacysandbox.google.com/cookies/chips)（privacysandbox.google.com）
- [Cookies Having Independent Partitioned State specification](https://www.ietf.org/archive/id/draft-cutler-httpbis-partitioned-cookies-01.html)（ietf.org）
- [Partitioned cookies](https://developer.mozilla.org/en-US/docs/Web/Privacy/Guides/Third-party_cookies/Partitioned_cookies)（developer.mozilla.org）
- [Storage Access API](/zh/reference/storage/storage-access-api/)
- [localStorage 与 sessionStorage](/zh/reference/storage/localstorage/)
- [逐出与尽力而为存储](/zh/reference/storage/eviction/)