同源策略

跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对 JavaScript 实施的安全限制。

这里额外了解一下同源和同站的区别,如下所示:

  • 同源:协议(protocol)、主机名(host)和端口(port)相同,则为同源;
  • 同站:有效顶级域名(Effective Top-Level-Domain,eTLD)和二级域名相同,则为同站。

同源策略限制了以下行为:

  • Cookie、LocalStorage 和 IndexDB 无法读取
  • DOM 和 JS 对象无法获取
  • Ajax 请求发送不出去
URL结果原因
http://store.company.com/dir2/other.html同源只有路径不同
http://store.company.com/dir/inner/another.html同源只有路径不同
https://store.company.com/secure.html失败协议不同
http://store.company.com:81/dir/etc.html失败端口不同 ( http:// 默认端口是 80)
http://news.company.com/dir/other.html失败主机不同

CORS标准

CORS 是一个 W3C 标准,全称是”跨域资源共享”(Cross-origin resource sharing)。

它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制。

浏览器一旦发现 AJAX 请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。

因此,实现 CORS 通信的关键是服务器。只要服务器实现了 CORS 接口,就可以跨源通信。

分为两种请求,一种是简单请求,另一种是非简单请求。只要满足下面条件就是简单请求

  • 请求方式为 HEAD、POST 或者 GET
  • http 头信息不超出一下字段:Accept、Accept-Language 、 Content-Language、 Last-Event-ID、 Content-Type(限于三个值:application/x-www-form-urlencoded、multipart/form-data、text/plain)

如果 Origin 指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

   Access-Control-Allow-Origin: http://api.bob.com
   Access-Control-Allow-Credentials:true
   Access-Control-Expose-Headers: FooBar
   Content-Type: text/html; charset=utf-8

上面的头信息之中,有三个与 CORS 请求相关的字段,都以 Access-Control-开头

  • Access-Control-Allow-Origin :该字段是必须的。它的值要么是请求时 Origin 字段的值,要么是一个*,表示接受任意域名的请求
  • Access-Control-Allow-Credentials: 该字段可选。它的值是一个布尔值,表示是否允许发送 Cookie。默认情况下,Cookie 不包括在 CORS 请求之中。设为 true,即表示服务器明确许可,Cookie 可以包含在请求中,一起发给服务器。这个值也只能设为 true,如果服务器不要浏览器发送 Cookie,删除该字段即可。
  • Access-Control-Expose-Headers:该字段可选。CORS 请求时,XMLHttpRequest 对象的 getResponseHeader()方法只能拿到 6 个基本字段:Cache-Control、Content-Language、Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在 Access-Control-Expose-Headers 里面指定。

withCredentials 属性

需要注意的是,如果要发送 Cookie,Access-Control-Allow-Origin 就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie 依然遵循同源政策,只有用服务器域名设置的 Cookie 才会上传,其他域名的 Cookie 并不会上传,且(跨源)原网页代码中的 document.cookie 也无法读取服务器域名下的 Cookie。

非简单请求

非简单请求是那种对服务器有特殊要求的请求,比如请求方法是 PUT 或 DELETE,或者 Content-Type 字段的类型是application/json

非简单请求的 CORS 请求,会在正式通信之前,增加一次 HTTP 查询请求,称为”预检”请求(preflight)。

OPTIONS /cors HTTP/1.1
   Origin: http://api.bob.com  //关键字段是Origin,表示请求来自哪个源。
   Access-Control-Request-Method: PUT
   Access-Control-Request-Headers: X-Custom-Header
   Host: api.alice.com
   Accept-Language: en-US
   Connection: keep-alive
   User-Agent: Mozilla/5.0...

除了 Origin 字段,“预检”请求的头信息包括两个特殊字段。

  • Access-Control-Request-Method:该字段是必须的,用来列出浏览器的 CORS 请求会用到哪些 HTTP 方法,上例是 PUT。
  • Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器 CORS 请求会额外发送的头信息字段,上例是 X-Custom-Header

服务器收到”预检”请求以后,检查了OriginAccess-Control-Request-MethodAccess-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
  • Access-Control-Allow-Methods:该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次”预检”请求。
  • Access-Control-Allow-Headers:如果浏览器请求包括 Access-Control-Request-Headers 字段,则 Access-Control-Allow-Headers 字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在”预检”中请求的字段。
  • Access-Control-Allow-Credentials:该字段与简单请求时的含义相同。
  • Access-Control-Max-Age:该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是 20 天(1728000 秒),即允许缓存该条回应 1728000 秒(即 20 天),在此期间,不用发出另一条预检请求。

一旦服务器通过了”预检”请求,以后每次浏览器正常的 CORS 请求,就都跟简单请求一样,会有一个 Origin 头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

CORS 与 JSONP 的使用目的相同,但是比 JSONP 更强大。JSONP 只支持 GET 请求,CORS 支持所有类型的 HTTP 请求。JSONP 的优势在于支持老式浏览器,以及可以向不支持 CORS 的网站请求数据。

跨域的解决办法

jsonp

jsonp 跨域其实也是 JavaScript 设计模式中的一种代理模式。**在 html 页面中通过相应的标签从不同域名下加载静态资源文件是被浏览器允许的,**所以我们可以通过这个“犯罪漏洞”来进行跨域。一般,我们可以动态的创建 script 标签,再去请求一个带参网址来实现跨域通信

虽然这种方式非常好用,但是一个最大的缺陷是,只能够实现 get 请求

// create script
script = document.createElement("script");
script.src = url;
target.parentNode.insertBefore(script, target);

CORS

后端配置 CORS 允许跨域即可

nginx 反向代理跨域

node 代理跨域

node 中间件实现跨域代理,是通过启一个代理服务器,实现数据的转发,也可以通过设置 cookieDomainRewrite 参数修改响应头中 cookie 中域名,实现当前域的 cookie 写入,方便接口登录认证。

iframe 跨域

document.domain + iframe 跨域
window.name + iframe 跨域//window.names属性值在文档刷新后依然存在的能力
location.hash + iframe 跨域

postMessage 跨域

HTML5 全新的 API

允许跨窗口通信,不论这两个窗口是否同源。

  // 接收来自于 iframe 的消息
      window.addEventListener("message", (data) => {
        // 通过 data.origin 来进行应用过滤
        if (
          data.origin === "<%= micro1 %>" ||
          data.origin === "<%= micro2 %>"
        ) {
          console.log("main: ", data);
        }
      });

WebSocket 协议跨域

WebSocket 是 HTML5 开始提供的一种在单个 TCP 连接上进行全双工通讯的协议。

webSocket 本身不存在跨域问题,所以我们可以利用 webSocket 来进行非同源之间的通信。

修改快捷方式

谷歌浏览器 快捷方式添加 —args —disable-web-security —user-data-dir

下面的那几种跨域看看就好了,平时主要使用的还是CORSJSONP代理

也有,不过跟 AJAX 的同源策略稍微有些不同:

  • 当请求 qq.com 下的资源时,浏览器会默认带上 qq.com 对应的 Cookie,不会带上baidu.com 对应的 Cookie
  • 当请求 v.qq.com 下的资源时,浏览器不仅会带上 v.qq.com 的 Cookie,还会带上 qq.com 的 Cookie

是存在同名属性值被覆盖的风险。

怎么解决

如果主应用和子应用同站,那么可以通过设置 Domain 使得两个应用可以共享彼此的 Cookie,例如上图中的 main-app-share 字段,通过设置 Domain 为 example.com 后可以被子应用进行共享。因此在同站的某些特殊情况下,Cookie 是可以在不同的应用中产生共享的。

Chrome 浏览器 80 版本将 SameSite 的默认值设置为 Lax,用于解决 iframe 应用产生 CSRF 跨站请求伪造的安全问题(防止跨站携带 Cookie)。此时解决方案是将 SameSite 设置为 None 并使用 Secure 属性(必须使用 HTTPS 协议)。

总结:

  • 主子应用同域:可以携带和共享 Cookie,存在同名属性值被微应用覆盖的风险
  • 主子应用跨域同站:默认主子应用无法共享 Cookie,可以通过设置 Domain 使得主子应用进行 Cookie 共享
  • 主子应用跨站:子应用默认无法携带 Cookie(防止 CSRF 攻击),需要使用 HTTPS 协议并设置服务端 Cookie 的 SameSite 和 Secure 设置才行,并且子应用无法和主应用形成 Cookie 共享
    • 这个将在未来也被 Chrome 禁止