Skip to content

同源策略、跨域资源共享、内容安全策略

发布日期:2023-04-23

最近测试环境项目被检测到不安全,要求加上CORS。此前是全放开,方便前端调试,加上以后出现了很多问题,因此来详细了解下相关的概念。


什么是同源策略

同源策略(same-origin policy)是浏览器执行的一种安全策略,同源指的是:

  • 协议相同
  • 域名相同
  • 端口相同

任何一个上述不相同,将会有以下限制:

  • cookie 不能获取
  • Localstorage, indexDB 不能获取
  • DOM 不能访问
  • AJAX 请求不能发送

两个网站一级域名相同(163.com)二级域名不相同(a.163.com和b.163.com),可以通过设置document.domain=163.com 来共享cookie。比如在a登录之后,在b网站也能获取登录态。注意,只有二级域名能设置一级域名作为document.domain,反之则不行。

举例:不同源无法读取 DOM 的两种场景

// 不同源的 window.open
window.parent.document.body // error
// 不同源的 iframe
document.getElementById("myIFrame").contentWindow.document // error

不同源可以采取的通讯的方法:window.postMessage

举例:不同源无法发送 AJAX 请求

同源政策规定 AJAX 只能发送给同源的网站,于是有了 jsonp 这个古老的解决方案,只能发送GET请求。

目前解决这个问题的方法,一个是websocket,另一个是下面将要讲的CORS。

阮一峰老师的浏览器同源政策及其规避方法

什么是跨域资源共享(CORS)

跨域资源共享(Cross-origin resource sharing)允许浏览器向跨源服务器,发出XMLHttpRequest请求,从而克服了AJAX只能同源使用的限制。

简单请求

简单请求通常是 GET、POST 请求(还有少用的HEAD),且 http 请求头不超出以下几种字段

  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain

从简单请求对 Content-Type 的限制可以看出,传统的表单(form)提交属于简单请求,而大多数现代表单提交方式通过 application/json 的方式来提交,就不属于简单请求了。

对于简单请求,浏览器会自动添加 Origin 请求头,如 https://yys.163.com, 该请求头是不可被页面 js 配置的(一些爬虫会自带和请求域名相同的 Origin 请求头以绕开 CORS 限制,比如 postman)。如果服务器允许这个跨域请求,则返回响应头 Access-Control-Allow-Origin 同样为 https://yys.163.com,如果是 * 表示允许所有跨域请求,通常在功能调试阶段(测试环境)会开启 *。

如果跨域请求需要带上 cookie,则需要双方的允许。一方面是在 Ajax 请求中添加请求头 withCredentials = true,另一方面需要服务器返回响应头 Access-Control-Allow-Credentials: true。值得注意的是,如果要发送 cookie,则响应头 Access-Control-Allow-Origin 不能为 *,在工作中曾经遇到过这个问题和后端调试了很久(苦笑)

非简单请求

最常见的非简单请求是 Content-Type 字段的类型是 application/json 的请求,在这种请求之前,浏览器会先发送一个方法为 Option 的请求给服务器,称为"预检"请求(preflight),要求服务器确认可以这样请求。

如果服务器允许请求,则返回 Access-Control-Allow-Origin 响应头(和简单请求一致),并返回 Access-Control-Allow-Methods 响应头表示允许哪些方法,避免每个方法都要问一遍。另外可能还有 Access-Control-Max-Age 响应头表示本次预检请求的有效期,在有效期内不需要再次发送预检请求,单位为秒。

阮一峰老师的跨域资源共享 CORS 详解

什么是内容安全策略(CSP)

内容安全策略(Content-Security-Policy,CSP)是一种白名单制度,明确了哪些外部资源可以加载执行。

背景:我的项目突然样式崩坏了,打开控制台发现console提示触发了内容安全策略,无法执行某些js,导致页面崩坏,观察到响应头中多了Content-Security-Policy,限制了非同源js的执行。这些js可以正常加载,但无法执行。类似的问题在我开发chrome浏览器插件升级到mv-3的时候遇到过,升级之后CSP变得严格,不能再使用vue2的即时编译了。

启用 CSP 有两种方式,一种方式是在响应头中 Content-Security-Policy 字段配置,另一种是在 Meta 标签中配置。

<meta http-equiv="Content-Security-Policy" content="script-src 'self'; object-src 'none'; style-src cdn.example.org third-party.org; child-src https:">

上述配置:

  1. script-src 'self' 表示脚本 <script> 只信任当前域名
  2. object-src 'none' 表示 <object> 标签不允许加载任何内容
  3. style-src cdn.example.org third-party.org 表示 <style> 只允许加载指定的两个域名的资源
  4. child-src https: 表示 <frame> 只允许使用 https 协议加载
  5. 其他没提到的资源不做限制

其他没有提到的配置参考 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

不同的配置用 ; 分隔,配置名和配置值用空格分隔,并列的多个值也使用空格分隔。配置值有很多种,其中只有 'self''none'是需要加引号的,分别表示限制当前域名、禁止加载任何资源。其他的配置值包括主机名、路径名、通配符、协议名。default-src 配置可以设置所有配置的默认值,如值为 self 则表示任何资源只允许加载本域名的。report-uri 配置还可以让浏览器向指定的服务器报告违反限制的行为,可以帮助我们及时发现一些搞破坏的行为。

其中 script-src 有以下几个特殊值,都需要放在单引号中

  1. unsafe-inline 允许执行页面使用 script 标签直接编写脚本,如下
<script>
  const inline = 1;
  // …
</script>

从这个配置值就明确告诉你,允许这种脚本是一种 unsafe 的行为,允许这个非常危险,如果一定要使用的话,可以使用下面两种方法

  1. nonce-EDNnf03nceIOfn39fn3e9h3sdfa 允许带有该 nonce(随机数) 的内嵌脚本,这个值比上面的安全一些
<script  nonce=EDNnf03nceIOfn39fn3e9h3sdf>
  const inline = 1;
  // …
</script>
  1. sha256-qznLcsROx4GACP2dm0UCKCzCG-HiZ1guq6ZZDob_Tng= 允许该 hash 值的代码执行,但不允许事件绑定。
<script>
  const inline = 1;
</script>

<!-- 不可以 -->
<button onclick="myScript()">Submit</button>
  1. unsafe-hashes 允许该 hash 的脚本执行的同时,允许事件绑定。比如 Content-Security-Policy: script-src 'unsafe-hashes' 'sha256-{HASHED_EVENT_HANDLER}'

  2. unsafe-eval 允许将字符串当做代码执行,如使用 eval() Function() setTimeout(这个方法可以这样调用setTimeout("alert(\"Hello World!\");", 500))等方法。说个题外话,由于 chrome extension 在 v3 版本对这种调用方式做了严格限制,因此在 v2 版本中使用 vue 模版的方式在 v3 不可行了,必须预先编译。

  3. wasm-unsafe-eval 允许 wasm 执行,是一个比上面的 unsafe-eval 更具体的描述,如果有上面的,则会覆盖这个规则,因为上面的规则更广泛

  4. strict-dynamic 严格动态,必须跟着 noncesha256 规则,表示只允许这个脚本和这个脚本引入的脚本,其他所有的脚本都不允许执行。比如 Content-Security-Policy: script-src 'strict-dynamic' 'nonce-someNonce'

  5. inline-speculation-rules 这个规则有关浏览器预加载推测,比如 Content-Security-Policy: script-src 'inline-speculation-rules' 'sha384-oqVuAfXRKap7fdgcCY5uykM6+R9GqQ8K/uxy9rx7HNQlGYl1kPzQho1wx4JwY8wC'

以上的 script 规则在不同的浏览器有不同的支持度,使用时需要具体分析

其他注意点

  1. 务必设置 script-srcobject-src, 除非设置了 default-src 因为只要能注入脚本,其他限制都可以规避

mdn 文档 https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Content-Security-Policy

阮一峰老师的Content Security Policy 入门教程

Power by vitepress