前端知库

第三方 Cookie、SameSite 和 Partitioned Cookie 有什么区别?

从嵌入式支付、评论和统计组件出发,区分跨站请求、第三方上下文、SameSite 限制和 CHIPS 分区 Cookie,说明 Partitioned 的存储键、部署要求、兼容性和替代方案。

参考答案

简答

  • SameSite 控制跨站请求是否携带 Cookie,Partitioned(CHIPS)按顶层站点分区存储 Cookie。
  • Chrome 放弃强制淘汰第三方 Cookie 后默认仍允许,Firefox 和 Safari 默认限制更严。
  • 分区 Cookie 只能在当前顶层站点内保存组件状态,不能跨站共享登录。
  • 跨站登录需要顶层页面配合,或使用 Storage Access API、FedCM;第三方不等于跨域。

一个评论组件运行在 comments.example,被嵌入到很多不同网站。它希望记住“当前评论框已登录”,但不应该通过一枚全局 Cookie 跟踪用户在所有顶层网站上的行为。传统第三方 Cookie 把状态按 Cookie 所属域保存,嵌入到不同顶层网站时可能读到同一份值。各浏览器对这类 Cookie 的默认策略不同:Safari 的 ITP 默认阻止第三方 Cookie,Firefox 的 Total Cookie Protection 默认按顶层站点隔离,Chrome 放弃了强制淘汰计划,默认仍允许,但无痕模式和用户、企业策略可以阻止。因此组件不能假设第三方 Cookie 一定可用,也不能假设一定被禁用。

要解决问题,先把三个概念分开:

  • 跨站请求:请求上下文中的顶层站点与目标站点关系不同,是否携带 Cookie 还要看 SameSite、请求方式和浏览器策略;
  • 第三方上下文:当前页面嵌入了另一个站点的资源,例如 iframe、图片或脚本,资源所属站点不是顶层站点;
  • 分区 Cookie:Cookie 仍由组件站点设置,但浏览器额外以顶层站点(scheme + 可注册域)作为分区键存储它,同一个组件在不同顶层站点中看不到彼此的值。Chrome 的实现还在分区键中记录是否存在跨站祖先帧。

分区不是授权机制,也不是让跨源脚本直接访问 Cookie。Cookie 依旧由浏览器在匹配请求时自动附带,JavaScript 能否读取仍受 HttpOnly、同源策略和 Cookie Store 等规则限制。

SameSite 控制发送条件,Partitioned 改变存储分区

传统 Cookie 的 SameSite 主要回答“这次请求在跨站上下文中是否允许携带 Cookie”。它不会把 Cookie 复制到多个独立存储桶,也不会改变 Cookie 的 Domain 和 Path 匹配:

http
Set-Cookie: session=opaque-id; Secure; HttpOnly; SameSite=Lax; Path=/

SameSite=None 表示不因 SameSite 属性本身限制跨站携带,但必须同时使用 Secure,并且仍可能受到浏览器第三方 Cookie 总策略、用户设置、企业策略和嵌入环境限制。SameSite=None 不代表“任何 iframe 都一定能读到或发送”。

Partitioned 则声明这个 Cookie 使用独立分区:

http
Set-Cookie: widget_session=opaque-id; Secure; HttpOnly; SameSite=None; Partitioned; Path=/

浏览器通常会按顶层站点和 Cookie 所属站点组合存储它。news.example 中的组件和 shop.example 中的同一个组件,即使 Cookie 名称和域相同,也不会共享这两个分区里的值。Partitioned Cookie 必须使用 Secure;跨站嵌入还要根据实际浏览器对 SameSite 和第三方上下文的要求配置,不能只加一个属性就宣布流程可用。

分区键是浏览器隐私模型的一部分,应用不能通过脚本读取、猜测或修改另一个顶层站点的分区。它可以减少跨站跟踪范围,却不会自动解决组件登录、权限和用户同意问题。

嵌入式登录为什么经常需要顶层页面配合

假设用户先在 accounts.example 登录,再在 merchant.example 的 iframe 中打开支付组件。组件请求 accounts.example 时,可能处于第三方上下文;即使 Cookie 设置了 SameSite=None; Secure,浏览器也可能因为第三方 Cookie 被禁用而不发送它。Partitioned Cookie 能让组件在每个顶层站点拥有独立会话,但它不会自动把顶层账号登录态复制进这个分区。

常见方案需要明确选择:

  1. 组件使用顶层导航或弹窗完成一次用户主动登录,再通过 postMessage 传递一次性授权码;
  2. 顶层页面调用自己的后端,由后端与组件服务端建立受控会话;
  3. 组件使用 Partitioned Cookie 维护每个顶层站点的独立状态,并在需要时让用户在该分区内完成授权;
  4. 不依赖 Cookie,改用短期令牌放在内存中,并通过明确的消息协议传给 iframe;
  5. 嵌入方确实需要访问自己未分区的 Cookie 时,在用户手势后调用 Storage Access API(document.requestStorageAccess())请求授权;
  6. 跨站登录场景可以评估 FedCM,由浏览器展示身份提供方的登录选择。

令牌传递要校验 origin、消息类型、请求 ID、过期时间和受众,不能因为“消息来自自己的 iframe”就信任任意 postMessage。跨站身份联合还要结合实际的 OAuth/OIDC 流程、PKCE、重放防护和服务端授权。

分区 Cookie 不是所有嵌入资源都能使用

浏览器只有在支持 CHIPS 和满足其安全条件时才会按分区语义处理 Partitioned。旧浏览器可能忽略未知属性,把 Cookie 当成普通 Cookie,或者完全拒绝第三方 Cookie;不同浏览器、隐私模式和企业策略也可能有不同结果。应用不能依赖“未知属性会被安全地当成分区”这种推断。

部署时至少要验证:

  • HTTPS、Secure、域名、Path、过期时间和响应是否真的被浏览器接受;
  • iframe、fetch、顶层导航和重定向分别处于什么站点上下文;
  • SameSite、credentials、CORS 和浏览器第三方 Cookie 策略是否同时满足;
  • 未支持 Partitioned 的浏览器是否有不泄露账号、仍可退出的降级流程;
  • 用户拒绝第三方存储、清理站点数据、使用无痕模式或切换顶层站点后,组件是否能恢复并解释状态。

开发者工具中应查看具体请求的 Cookie 阻止原因和存储分区,而不是只看 document.cookie。HttpOnly Cookie 本来就不会出现在 document.cookie;分区 Cookie 也不能通过读取字符串来判断是否属于预期顶层站点。

认证 Cookie 和组件状态不要混成一个值

一个嵌入式组件可能需要两类状态:顶层产品的登录授权,以及组件在当前顶层站点内的偏好、草稿或会话。把长期主账号 Cookie 直接放到所有嵌入上下文,会扩大泄露和跟踪风险;把所有状态都塞进 Partitioned Cookie,又可能导致用户在不同网站上被当成不同账号。

可以让服务端签发短期、受众明确的组件会话,并把用户身份和顶层站点绑定:

http
Set-Cookie: widget_session=短期随机标识; Secure; HttpOnly; SameSite=None; Partitioned; Path=/

服务端仍必须验证会话是否属于当前租户、组件实例和授权范围。Cookie 的分区只改变浏览器存储和发送上下文,不能替代数据库中的租户隔离、服务端权限判断、CSRF 防护或审计记录。

对跨站写操作,不能因为用了 Partitioned 就省略 CSRF 和来源校验。分区 Cookie 仍可能随符合条件的请求自动发送,攻击者也可能诱导嵌入上下文发起请求。根据场景使用 Origin/Referer 校验、CSRF Token、Fetch Metadata 和明确的 CORS 凭据策略。

不要把“第三方”理解成“跨域”

app.example.com 和 api.example.com 是不同源,但通常属于同一站点;两个完全不同的注册域名才通常是跨站。CORS 处理的是脚本能否读取跨源响应,SameSite 和第三方 Cookie 策略处理的是请求上下文中的 Cookie 发送,Partitioned 处理的是 Cookie 在第三方上下文中的存储分区。它们不能互相替代:

  • CORS 允许读取响应,不会强迫浏览器发送被策略阻止的 Cookie;
  • credentials: 'include' 表示请求希望带上凭据,不会绕过 SameSite 或第三方 Cookie 禁止;
  • SameSite=None 允许更宽的跨站携带条件,但不关闭浏览器的隐私限制;
  • Partitioned 让不同顶层站点的组件状态隔离,不会让任意跨源脚本获得 Cookie 内容。

排查问题时,应分别记录请求的顶层站点、目标站点、源、Cookie 属性、credentials、响应头和浏览器阻止原因。只修改 CORS 头或只把 SameSite 改成 None,往往会掩盖真正的存储分区和认证设计问题。

选型和验收

如果组件只需要在当前顶层网站记住自己的状态,Partitioned Cookie 可能适合;如果用户必须跨多个顶层网站共享同一个登录会话,应优先设计显式授权流程,而不是试图绕过浏览器隐私边界。对于广告、统计和跟踪用途,更不能把分区机制当成恢复跨站识别能力的办法。

验收至少包括:

  • 同一组件分别嵌入两个顶层站点,确认两边的分区值互不可见;
  • 关闭第三方 Cookie、使用无痕模式、清理站点数据和禁用存储时,确认流程有可理解的降级;
  • iframe、fetch、重定向和顶层导航分别测试 Cookie 是否携带;
  • 多租户、退出登录、会话过期和切换账号后,旧分区不会继续授权新用户;
  • CORS、CSRF、Origin 校验、postMessage 和短期令牌的边界都有独立测试。

第三方 Cookie 的变化不是简单地把 SameSite 改成 None。前端需要先确定状态是否应该跨顶层站点共享,再选择普通 Cookie、分区 Cookie、顶层授权或无 Cookie 令牌方案,并把浏览器隐私策略视为正常产品条件,而不是偶发异常。