八股文实战—cors/cross-site/cookie/same-site/same-party

  • canvas中使用跨域图片如何做认证
  • 浏览器什么时候认为请求是cross-site,此时如何携带cookie
  • 同名cookie如何确定优先级
  • SameParty提案为了解决什么问题

跨域、cookie是校招面试高频基础问题。本文用一个模拟案例,希望能让我们在MDN之外,有个实战的理解,为面试出题提供新的素材。

相关内容:跨域,跨站, cookie,SameSite,SameParty

项目起步

开发一个网站,部署在shop.taobao.com上,所有img等资源和后端接口都通过https://shop.taoba...

同源策略:SOP是同源策略(Same origin policy)的缩写,是一种由美国网景通信公司提出的安全策略。其原理是在Web浏览器中,不同源的文档脚本之间不能相互访问。这种策略可以有效防止跨站脚本攻击。

前后端分离

随着项目推进,前端服务器和后端接口压力不同,不想再用前端服务器反向代理后端接口,开始前后端分离,后端独立部署于api.taobao.com。此时域名不同,导致跨域。此时可以在devtools网络面板中看到请求标头的

Sec-Fetch-Mode: cors,控制台也会有相应报错。

跨域限制

  1. DOM:禁止操作非源页面的DOM与JS对象

这里主要场景是iframe跨域的情况,非同源的iframe是限制互相访问的

  1. XmlHttpRequest:禁止使用XHR对象向不同源的服务器发起HTTP请求,即不能发送跨域ajax请求
  2. 本地存储:Cookie、LocalStorage和IndexDB无法跨域读取

解决方式除了代理到同源,常用的就是后端配合或jsonp,本文不讨论jsonp。

后端设置允许跨域

设置允许的源

makefile 复制代码
Access-Control-Allow-Origin: <origin> | *

表示允许跨域访问的单一源,或则为所有源,但是不安全,不建议使用。

如果是简单的GET请求,api.taobao.com想被https://shop.taoba...

那如果另一个域名 mall.taobao.com也想访问后端接口https://api.taobao...

设置完origin之后有些get请求已经可以正常访问了,可是有些post却不行。

设置允许的方法和标头

预检

查看网络面板可以看到这些失败的请求之前都发送了OPTIONS预检请求。

与简单请求不同,"需预检的请求"要求必须首先使用 OPTIONS 方法发起一个预检请求到服务器,以获知服务器是否允许该实际请求。"预检请求"的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响。

简单请求需要满足以下几个条件,不满足条件的都是"需预检的请求"

  1. 请求方法
  • get
  • head
  • post
  1. http的头信息不超过以下几种字段
  • Accept
  • Accept-Language
  • Content-Type:(只限于application/x-www-form-urlencoded、multipart/form-data、text/plain)
  • Content-Language
  • Last-Event-ID
  1. 请求中的任意 XMLHttpRequest 对象均没有注册任何事件监听器;XMLHttpRequest 对象可以使用 XMLHttpRequest.upload 属性访问。

  2. 请求中没有使用 ReadableStream 对象

现在大多数接口都是content-type: application/json,那自然就会触发OPTIONS预检。

我们经常使用的常见的复杂请求有:

  1. 请求方法为put或delete

  2. Content-Type字段类型为:application/json

  3. 添加额外的http header 比如:access_token

OPTIONS预检请求会发起一次空body的OPTIONS预检请求,会带着这两个header:

xml 复制代码
Access-control-request-headers: <header-name>[, <header-name>]*

触发非简单请求的头,比如自定义头、或content-type为 application/json。如果没有就不发送。

sql 复制代码
access-control-request-methods: <method>[, <method>]*

将要跨域请求的方法,比如POST

预检响应

此时服务器要处理options请求,返回相应的头:

makefile 复制代码
Access-Control-Allow-Origin: <origin> | *

同上

sql 复制代码
Access-Control-Allow-Methods: <method>[, <method>]* 

允许跨域访问的方法,GET、POST等

xml 复制代码
Access-Control-Allow-Headers: <field-name>[, <field-name>]*

同上,允许跨域访问的头,如content-type

makefile 复制代码
Access-Control-Max-Age: <delta-seconds>

浏览器的预检请求可以通过设置Access-Control-Max-Age进行缓存

很多后端为了省事,并没有根据方法单独处理,针对跨域都统一加了

makefile 复制代码
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: *

假设我们的后端接口api.taobao.com也是这样设置的。

cookie跨域认证

普通浏览接口现在都正常了,但是对于一些需要认证的接口无法通过认证了,cookie(cookie的具体配置后文再展开)跨域无法发送了。

Credentials

后端

yaml 复制代码
Access-Control-Allow-Credentials: true

允许携带认证cookie

此时以下参数也不能再设置为*了,需要明确指定

makefile 复制代码
Access-Control-Allow-Origin: *
Access-Control-Allow-Headers: *
Access-Control-Allow-Methods: *

Access-Control-Allow-Origin 根据请求来源动态填写,注意白名单控制

其他两项根据实际情况填写

注意:后端接口往往经过 网关 和代理,如果Access-Control-Allow-Origin Access-Control-Allow-Credentials被添加多次,也会导致跨域问题

csharp 复制代码
has been blocked by CORS policy: The value of the 'Access-Control-Allow-Credentials' header in the response is 'true, true' which must be 'true' when the request's credentials mode is 'include'.

前端

前端需要显示的声明需要携带cookie

XMLHttpRequest

ini 复制代码
const xhr = new XMLHttpRequest()
xhr.withCredentials = true

Axios

csharp 复制代码
axios.get(path, {
  withCredentials: false
})

fetch

perl 复制代码
fetch("https://example.com", {
  credentials: "include", // "same-origin" 只能同源携带
});

静态资源

随着业务发展,对资产的安全性有了一定要求。对于图片等资产也需要认证,如果认证token像ajax接口一样放在header里就行不通了,放在query里给客户的感觉也像没认证一样,毕竟复制链接就可以发送查看。所以像上述接口一样,统一采用cookie认证。

img标签

如果是同源图片,放在shop.taobao.com,cookie可以自动携带。为了优化网络加载,图片放到了 cdn.taobao.com (注意都是.taobao.com)上,此时跨域了怎么办,我们会发现,html上的img标签中的资源已经携带了cookie,查看网络面板

Sec-Fetch-Mode是no-cors,浏览器对于img,js等自身请求的资源是不当做跨域处理的,这也是jsonp的原理。(这里提个问题,如果域名是cdn.aliyun.com 需要注意什么呢)

canvas、webgl

对于普通图片,可以不做任何处理,但是业务中使用了canvas绘制图片,webgl中使用了图片作为贴图,canvas 使用跨域图片是被污染状态,不能使用getImageData toDataURL等功能,在webgl中直接使用跨域图片也是不行的。比如:

canvas.toDataURL()导出图片会报错:Uncaught DOMException: Failed to execute 'toDataURL' on 'HTMLCanvasElement': Tainted canvases may not be exported.

webgl中使用跨域图片报错:Uncaught DOMException: Failed to execute 'texImage2D' on 'WebGLRenderingContext': The image element contains cross-origin data, and may not be loaded.

对于这些图片需要显示指定img标签或Image对象使用跨域模式:

ini 复制代码
<img crossorigin="" src="" alt="">
<img crossorigin="anonymous" src="" alt="">
<img crossorigin="use-credentials" src="" alt="">
ini 复制代码
const img = new Image()
img.crossorigin = ''
img.crossorigin = 'anonymous'
img.crossorigin = 'use-credentials'
  • anonymous 对此元素的 CORS 请求不携带cookie。
  • "" 设置一个空的值,如 crossorigin 或 crossorigin="",和设置 anonymous 的效果一样。
  • use-credentials 对此元素的 CORS 请求将携带cookie。

我们的使用场景下,需要使用携带cookie,所以此处设置为 use-credentials,网络面板可以看到

Sec-Fetch-Mode是cors,cookie也正常携带了,canvas和webgl也能够正常使用图片。

注意缓存导致跨域

同一资源的跨域和非跨域版本

因为通常img不会刻意设置crossorigin,那么如果该资源也被canvas或webgl使用,被设置为use-credentials进行加载,那么就要小心缓存带来的问题,因为本地存在了非跨域版本的缓存,请求跨域版本的时候直接读取缓存,就会加载该版本导致跨域报错。解决方式:

  • 统一加crossorigin:注意历史缓存问题
  • 禁用缓存:影响性能
  • 跨域版本图片加query参数如 cdn.taobao.com/img.jpg?cor... ,以和非跨域版本分别缓存:改动相对较少,影响性能

网站升级

之前的一批图片资源没有做跨域,客户端缓存。网站升级后采用跨域方式加载,也会遇到上面的问题,此时也需要留意处理,方式同上。

切换域名导致缓存跨域

当前页面是shop.taobao.com,加载了跨域图片https://cdn.taobao...

Vary

Vary 决定了对于未来的一个请求头,应该用一个缓存的回复 (response) 还是向源服务器请求一个新的回复。它被服务器用来表明在 content negotiation algorithm(内容协商算法)中选择一个资源代表的时候应该使用哪些头部信息(headers).

通俗的理解就是哪些头在被缓存的时候需要考虑,被当做缓存key,以判断是否视为同样的文件。

我们的案例为例,想要不同origin的请求得到不同的结果,设置 Vary: Origin,那么不同域下的请求就会当做不同的缓存,不会再击中同一份缓存,也就解决了跨域问题。

题外话:Vary不止用在浏览器,服务端的代理缓存同样有用,比较常用的是Vary: User-Agent,如果pc版和移动版同样路径下后端输出不同的html,或是不同浏览器下的文件有区别,nginx代理的时候就会只缓存一份,就会出现问题。那么该设置就可以缓存不同的html,防止缓存错乱。

cookie跨站认证

业务进一步发展,cdn 从cdn.taobao.com迁移到了https://cdn.aliyun...

观察面板可以看到:

Sec-Fetch-Site: cross-site 跨站了,什么是跨站?

跨站

Cookie 遵守同站策略,而非同源策略,Cookie并不区分端口与协议。同站同源两者的区别主要在于判断的标准是不一样的。一个 URL 主要有以下几个部分组成:

可以看到同域的判断比较严格,需要 protocol、 hostname、port 三部分完全一致。

跨站判断主要是根据 Mozilla 维护的公共后缀表(Pulic Suffix List)使用有效顶级域名(eTLD)+1的规则查找得到的一级域名是否相同来判断是否是同站请求。eTLD 的出现主要是为了解决 .``com.cn、 .``com.hk、 .co.jp 这种看起来像是二级域名的但其实需要作为顶级域名存在的场景。

有效顶级域名eTLD

域名可以分成顶级域名(TLD Top-Level Domain)、二级域名、三级域名等等,如:

顶级域名:.com, .cn, .top, .xyz, .io

二级:baidu.com, bilibili.com

三级域名:xx.baidu.com xx.bilibili.com

.com是在 PSL 中记录的有效顶级域名eTLD,shop``.taobao.com 和 cdn.taobao.com 就是同站域名

.github.io 是有效顶级域名eTLD,所以 a.github.io 和 b.github.io 就是跨站域名

Cookie属性

那么如何向跨站域名发送cookie呢,其实这就是常说的第三方cookie,现在safari已经默认禁止,chrome的无痕模式默认也是禁止的,我们先忽略。

这里就要展开cookie的具体属性了。

Secure

Secure 属性是防止信息在传递的过程中被监听捕获造成信息泄漏。当 Secure 标志的值被设置为 true 时,表示创建的 Cookie 会被以安全的形式向服务器传输,即只能在 HTTPS 连接中被浏览器传递到服务器端进行会话验证,如果是 HTTP 连接则不会传递该信息,所以 Cookie 的具体内容不会被盗取,该属性只能在 HTTPS 站点(localhost不受影响)下被设置。

SameSite

首先我们最关心的影响跨站的属性,SameSite,三个可选值:

  • None
  • Lax 默认值
  • Strict

None

在 Chrome80 版本以前,Same-Site 的默认值是None , 该属性值表示不做任何限制,允许第三方Cookie 。

何时写入第三方cookie

cookie的domain的设置是有限制的,只能设置自身域内(被请求方的域下)。打开Devtools面板,什么时候我的网站会列出出现第三方Cookie?

  • 通过img标签引入一个外域的图片,或是iframe等等,服务端响应通过set-cookie设置了Cookie,并且设置SameSite为None,此设置同时要求Secure为true。
  • iframe的网页内通过js写入SameSite为None,Secure为true的cookie,此时面板中会单独列出来:
  • 请求一个跨域请求,服务端响应通过set-cookie设置了Cookie,并且设置SameSite为None,此设置同时要求Secure为true。并且,满足跨域携带cookie的前后端配置,(上文中的 《cookie跨域认证》章节),特别注意前端ajax配置对应的credentials,此配置在跨域写入cookie时也是必须的。

Lax

Lax会对一部分第三方Cookie进行限制发送,在 Chrome 80 中浏览器将默认的 SameSite 规则从 SameSite=None 修改为 SameSite=Lax。设置成 SameSite=Lax 之后页面内所有跨站情况下的资源请求都不会携带 Cookie,但是导航到目标网址的 Get 请求除外------也就是:链接(点击链接相当于在当前页发起get跨域请求后跳转),预加载请求,GET 表单。具体如下:

Strict

Strict 最为严格,它完全拒绝第三方站点,实际运用场景并不多,当某些 Cookie 被设为Strict后,可能会影响到用户的体验。比如我在baidu.com 中用a标签 链接到bilibili ,而bilibili的token如果是Strict的话,那我跳转过去就会丢失登录状态。

Domain

Domain 指定了哪些主机可以接受 Cookie。如果不指定,该属性默认为同一 host 设置 cookie,不包含子域名 。如果指定了 Domain,则一般包含子域名。

假设我们在www.taobao.com 页面

set-cookie时如果指定了domain,则按照指定domain 设置,如果不以.开头,则浏览器会自动在前面加.,此时所有子域名都可以使用此cookie,如:

ini 复制代码
document.cookie="key=value;domain=taobao.com"

此时 shop.taobao.com cdn.taobao.com 都可以使用此cookie。

set-cookie时如果没有指定domain,则按照完整域名写入,不包含子域名。如:

ini 复制代码
document.cookie="key=value;"

此时只有www.taobao.com可以使用此cookie。

Path

Path 属性指定了一个 URL 路径,该 URL 路径必须存在于请求的 URL 中,以便发送 Cookie 标头。以字符 ("/") 作为路径分隔符,并且子路径也会被匹配。

Path 决定具体请求哪个路径时会被携带。

如何匹配

例如,设置 Path=/docs,则以下地址都会匹配:

  • /docs
  • /docs/
  • /docs/Web/
  • /docs/Web/HTTP

但是这些请求路径不会匹配以下地址:

  • /
  • /docsets
  • /fr/docs

当未设置Path或者设置为空时,Path 会被设置为当前请求路径

注意点:

  • 当请求地址不带末尾的/ 时,www.a.com:3000/a/b
  • 当请求地址末尾带/ 时, www.a.com:3000/a/b/

同名cookie优先级

Cookie是由Domain 与 Path 来区分的,因此不同的Domain或Path 会被识别成不同的Cookie, 所以你可能会遇到多个同名的情况,那么优先级如何确定呢

path不同

如果path不同,则匹配越精确,排序越靠前,后端拿到的是path匹配最精确的值,不考虑domain。

path相同

比如在shop.taobao.com 同path下写入两个cookie:

此时请求shop.taobao.com上资源,key取哪个cookie呢,因为.shop.taobao.com更精确所以取value2么?查看网络面板发现:

value1排在前面,后端拿到的值也是value1,那么这个排序是如何决定的呢,这个顺序是 cookie 更新顺序决定的 ,先更新或写入的cookie排在前面,也就是说后端拿到的是最早更新或写入的cookie,不考虑domain。

Expires 和 Max-Age

Expires 与 Max-Age属性定义了 Cookie 的生命周期,也就是浏览器应删除 Cookie 的时间。在默认情况下Cookie 的生命周期是 Session 级别,即退出浏览器后自动过期。 与 Http Cache 类似, Expires 是以一个绝对GMT格式的时间的来指定过期时间,而 Max-Age 是以多少秒后过期。Max-Age 是 http1.1 的产物,优先级比 Expires 要高

删除cookie

过期时间经常用来删除cookie,只有将 Cookie 设为过期才会删除, 注意只有符合指定 domain 与 path 会被删除。

HttpOnly

HttpOnly 要求浏览器不要通过 HTTP(和HTTPS)以外的渠道使用 Cookie,也就是说只能通过 Http 的响应头里进行Set-Cookie , 用户无法在 js 代码中去操作与读取该 Cookie。这个属性主要是用来缓解 XSS 攻击的。

跨站认证

回到最初的问题,shop.taobao.com 访问 cdn.aliyun.com资源,并认证,如何解决?

只需要在最开始调用cdn 登录接口,通过body或header传递认证token,并通过set-cookie写入属性为SameSite=None,Secure=true的token,后续资源就可以携带认证cookie了。(或者采用iframe等其他写入第三方cookie的方式)

限制

虽然看起来解决了问题,但是safari已经默认禁用第三方cookie,chrome的无痕模式默认情况下也禁用第三方cookie。有些网站针对禁用提示用户,比如刚刚开始禁用时的淘宝,就是采用提示用户取消禁用的形式来规避问题。很多单点登录也是依靠第三方cookie的,则改为了登陆跳转过程中携带code的方式。具体此处不展开。

其他方案

因为限制第三方cookie是趋势,所以除非不得已还是尽量不要依赖第三方cookie的认证方式,可以选择将跨站资源代理到同站来避免。

但是,如果后端有大量深度不同的全路径资源的接口,如:

css 复制代码
{
    a: {
        b: {
            c: 'https://cdn.aliyun.com/img.jpg'
        }
    }
}

此时没办法直接做代理,而且后端和前端replace url,或刷数据的成本都比较高,后续添加接口维护的心智负担也比较重。也可以尝试重定向的方式来作为短期方案。

重定向

  1. 申请同站域名:storage.taobao.com
  2. 将来源是shop.taobao.com 的对 cdn.aliyun.com的请求,重定向到https://storage.ta...

此时同一个资源会发送两次:

第一次:重定向

后端判断后重定向到同站地址:storage.taobao.com

此时要注意,因为需要跨域,进行重定向的地址也要添加相应的跨域header,以运行shop.taobao.com访问

第二次:真实请求

第二次访问重定向后的同站地址storage.taobao.com,就可以正常发送cookie了。

此时要注意,chrome对于重定向的请求,会设置origin为null:

那么对应的跨域资源接口的Access-Control-Allow-Origin需要根据请求来源进行设置,或者设置为null,而不能设置为当前访问页面(shop.taobao.com)

当然如果直接重定向到前端服务器shop.taobao.com,再做代理,可以省略跨域设置。

Service Worker

大量的重定向会多很多无用请求,并不是一个长久之计。对请求资源做拦截会想到service worker,在service worker中替换来达到本地重定向的效果,但是需要注意service worker的安装激活是需要时间的,首次访问的加载时序需要解决,而且需要权衡引入service worker造成的其他相关成本,本文不展开。

SameParty提案

针对第三方cookie的限制本意是保护隐私,但是对正常使用的产品造成了很多不便,所以有了SameParty的提案,在上面正常的业务场景中,所有不同的域名基本上都来自同一个组织或企业,我们希望在同一个运营主体下不同域名的 Cookie 也能共享。

如何定义 First-Party Sets

每一个需要用到 First-Party Sets 策略的域名都应该把一个 JSON 配置托管在 /.well-known/first-party-set 路由下。

例如 shop.taobao.com 的配置应该托管在 https://``shop.taobao.com``/.well-known/first-party-set 下:

json 复制代码
{
    "owner": "taobao.com",
    "version": 1,
    "members": ["aliyun.com"]
}

另外 aliyun.com 也需要增加所有者的配置:

json 复制代码
{
    "owner": "taobao.com"
}

SameParty设置

ini 复制代码
Set-Cookie: key=value; Secure; SameSite=Lax; SameParty
  • SameParty Cookie 必须包含 Secure.
  • SameParty Cookie 不得包含 SameSite=Strict.

Chrome 可以通过开启该选项来试用。

回顾

  • 什么时候会发送预检,预检响应需要设置哪些header
  • canvas中使用跨域图片如何做认证
  • 浏览器什么时候认为请求是cross-site,此时如何携带cookie
  • 同名cookie如何确定优先级
  • SamePart提案为了解决什么问题
相关推荐
wangruofeng2 小时前
AI Models 1.1.0 发布,查大模型价格、对比模型和找 API 地址
前端·aigc·ai编程
脚本自动化开发2 小时前
玩影之刃零,两张四千元级显卡怎么挑
后端
wangruofeng2 小时前
给前端开发者的 100 个设计参考网站
前端·设计
星栈5 小时前
视频即代码,Remotion 能做什么、不能做什么
前端·音视频开发
广州华水科技5 小时前
单北斗GNSS位移监测系统的应用与技术解析
前端
用户47949283569156 小时前
从一句话或代码仓到可交互 HTML:开源 Agent 画图技能 Archify v3.2 实测与选型边界
前端·后端·面试
努力的小雨7 小时前
别再复制日志给AI了!我让Codex自己去观测云查报错
后端
风骏时光牛马8 小时前
生成一个AI多模态标题,仅输出一条标题,无额外文字、问句、备选、配图、文案等任何附加内容,不需要其他的任务服务 只要标题。
前端
wangruofeng8 小时前
从 GIF 到 AVIF:网页图片格式演进与 2026 年选型指南
前端·架构
afumu9 小时前
Go 写一个只监听 127.0.0.1 的控制服务:鉴权、白名单、危险命令拦截
后端·安全·go