logo
Published on

三方cookie问题

  • Author Avatar
    Author
    Author Link

要实现三方cookie的携带和写入,要注意几个响应头。和一个请求模式。

条件

  1. 服务器一域名 http://localhost:4321
  2. 服务器二域名: http://localhost:5502(取决于 live-server 的插件)

正常情况(同源)

同源。从 4321 打开网页,并且请求。 此时服务器代码为:

  if (req.url.startsWith('/api')) {
    // set cookie
    res.setHeader('Set-Cookie', 'key=value;')
    // 返回 JSON 响应
    res.setHeader('Content-Type', 'application/json')
    res.writeHead(200)
    res.end(JSON.stringify({ succ: true, msg: 'hello world' }))
  }

客户端请求代码:

  cbtn.addEventListener('click', () => {
    fetch('http://localhost:4321/api')
  })

就是比较正常的情况。此时做请求结果如下:

第一次请求:

正常被设置了 cookie

5a905f2be1fd566804f306725febd40f_MD5

第二次请求:

正常携带了cookie

08739c9a780f7cb917bdaa2cdbf20b35_MD5

场景

场景一:直接请求三方服务器(跨域请求)

首先解决跨域问题,再实现上面的一系列操作。

此时服务器代码:

  if (req.url.startsWith('/api')) {
    // cors
    res.setHeader('Access-Control-Allow-Origin', '*')

    // set cookie
    res.setHeader('Set-Cookie', 'key=value;') // 确保格式正确
    // 返回 JSON 响应
    res.setHeader('Content-Type', 'application/json')
    res.writeHead(200)
    res.end(JSON.stringify({ succ: true, msg: 'hello world' }))
  }

无论请求多少次,都无法正常设置cookie

b6e8ddcf49c59ac7d23c9a1e687761d2_MD5

但是本地写入cookie是没问题的。document.cookie="xx=xx"

解决

方法一: CORS (cross-origin-resource- sharing)

客户端

  1. 开启请求的 credentials 模式 允许请求携带凭证。

    fetch('http://localhost:4321/api', {
      credentials: 'include'
    })
    
    axios.get('xxxx', {
      // `withCredentials` 表示跨域请求时是否需要使用凭证
      withCredentials: true, // default 为 false
    })

服务端

  1. 设置 Access-Control-Allow-Credentials​ 如果客户端请求设置了 credentials ,但是响应头没有 Access-Control-Allow-Credentials​ 的话,会抛错 请求的 credentials 和 此响应头 是配套的

    res.setHeader('Access-Control-Allow-Credentials', 'true')
  2. 设置 Access-Control-Allow-Origin​ ​57772c09784b5be16ffb768a1d1e6d79_MD5​ 以上CORS错误,当请求的 credentials mode 为 include 时。Access-Control-Allow-Origin​ 不能为通配符。 所以,设置 Access-Control-Allow-Origin​ 为被允许的站点。
    - res.setHeader('Access-Control-Allow-Origin', '*')
    + res.setHeader('Access-Control-Allow-Origin', 'http://localhost:5502')
方法二: 代理

做一个代理服务器,把例如 localhost:4321/trd-cookie 代理当前 5502 的服务器上。

场景二:iframe 嵌入

特殊情况

前言:

根据 gpt 所说,出现如下情况,是因为浏览器的宽松策略把 localhost 作为同源看待。或许只会出现在 localhost 上。

场景描述:

例如我在 5502 中嵌入了一个 4321 的网页。此时需要 4321 发送请求给 4321/api,此接口响应头会 Set-Cookie。

结果:

如上场景我发现。直接请求即可。直接 Set-Cookie 可以让 5502 和 4321 都能获取到 cookie。即使 SameSite 设置为 Strict

服务端代码:

  if (req.url.startsWith('/api')) {
    // set cookie
    res.setHeader('Set-Cookie', 'key=value;SameSite=Strict;')
    // 返回 JSON 响应
    res.setHeader('Content-Type', 'application/json')
    res.writeHead(200)
    res.end(JSON.stringify({ succ: true, msg: 'hello world' }))
  }

客户端代码:

// iframe 中
fetch('http://localhost:4321/api')

如下图,5502 也被正常设置到了 cookie,并且能够获取到这个 cookie

cab656910e6332539804e5a16b5e18de_MD5

并且,5502在发送请求 (到 5502 的接口) 的时候,也可以正常携带 cookie

791a4064d09d7e3025acb35624e2cc14_MD5

强模拟情况

使用 虚拟机 + 更改 host 来模拟这种情况

场景描述

父网页:

域名 ==> https://licuiivm.com

子项目:

域名 ==> http://localhost.com

请求 ==> http://localhost.com/api

可以看到如下情况

0debd167e5a649f135a3de159767a6d3_MD5

如下图所示,cookie 已经被添加了,但是会被屏蔽掉。并不会放入请求头中去

5c2098e2ae2b611e0408a0caf6efa6d1_MD5

既然如此,修改 SameSite 的值试试。将 4321 服务器的 Set-Cookie 加上 SameSite=None。

89f62fe9a35494796d033ae6f51c1235_MD5

如上,当 SameSite 设置为 None 时,必须使用 Secure。那么再次设置 Secure

如图,正常被设置并且第二次请求时可以正常携带。

c232949228ac046cbd86bd021f64e672_MD5
aa589a17cd5154fb81c4c34c4e7cca05_MD5

此时服务端代码为:

  if (req.url.startsWith('/api')) {
    // set cookie
    res.setHeader('Set-Cookie', 'key=value;SameSite=None;Secure;')
    // 返回 JSON 响应
    res.setHeader('Content-Type', 'application/json')
    res.writeHead(200)
    res.end(JSON.stringify({ succ: true, msg: 'hello world' }))
  }

客户端代码:普通请求即可

fetch('http://localhost:4321/api')
拓展 Partitioned

虽然上述场景已经OK,但是还是有一个警告。

c37b97fc9fe77a8d36160bad4bfe0cca_MD5

其解决的是一个分区的问题。如下场景:

  1. 一个用户访问 https://site-a.example​,该站点嵌入了 https://3rd-party.example​ 的内容。 https://3rd-party.example​ 在用户设备上设置了 cookie。
  2. 该用户访问 https://site-b.example​,该站点同样嵌入了 https://3rd-party.example​ 的内容。 ​https://3rd-party.example​ 的新实例仍可以访问用户在上一页(site-a.example)时设置的 cookie。

有关 cookie 分区:Partitioned

总结

三方cookie获取的两种场景:

场景一:跨域资源请求 场景二:iframe获取

解决方案:

场景一:通过 CORS(cross-origin-resource-sharing) 进行跨域资源共享 场景二:通过 Set-Cookie 的 SameSite 和 Secure 进行设置

CORS

服务端

以下两个请求头必须设置正确:

  1. Access-Control-Allow-Credentials 此响应头和请求的 credentials[凭证] 模式挂钩。 ​097c993098506ae4b6607b8205670113_MD5
  2. Access-Control-Allow-Origin 也是和请求的 credentials 模式挂钩,当为 include 时。不能使用通配符。

客户端

如果希望向三方服务器获取 cookie,则必须开启 credentials(fetch)/ withCredentials(xhr)

credentials​ 是 Request​ 接口的只读属性,用于表示用户代理是否应该在跨域请求的情况下从其他域发送 cookies。这与 XHR 的 withCredentials 标志相似,不同的是有三个可选值(后者是两个Boolean):

  • omit​: 从不发送 cookies.
  • same-origin​: 只有当 URL 与响应脚本同源才发送 cookies、HTTP Basic authentication 等验证信息.(浏览器默认值,在旧版本浏览器,例如 safari 11 依旧是 omit,safari 12 已更改)
  • include​: 不论是不是跨域的请求,总是发送请求资源域在本地的 cookies、HTTP Basic authentication 等验证信息。

Iframe

iframe 嵌入,即使被嵌入的网页和请求的接口是同源的

如:iframe.src=http://localhost:4321.com,请求接口为 http://localhost:4321/api

也无法正常被设置cookie

此时,Set-Cookie 除了 cookie 值之外,还需要设置三个:

  • SameSite=None
    • 默认为 Lax,意味着 cookie 不会在跨站请求中被发送,如:加载图像或框架(frame)的请求。
    • 设置为 None 时,必须同时设置 Secure​ 属性,就像这样:SameSite=None; Secure​。
  • Secure
    • 表示仅当请求通过 https:​ 协议(localhost 不受此限制)
  • Partitioned