# 跨域

浏览器为了防止跨源网络、跨源 API、跨源数据存储访问的攻击,做了同源策略限制,限制了从用一个源加载的文档或脚本如何与另外一个源的资源进行交互,用于隔离潜在恶意文件的重要机制。不同协议、不同域名、不同端口就构成了跨域。

# JSONP

在 html 中,一下标签比如 script、img 等没有跨域限制的。可以通过 script 等标签发送跨域的请求,后面参数跟着一个回调函数的名字

  <script>
    window.myCallback = (res) => {}
  </script>
  <script src="https://xxx.com?msg=hello&cb=myCallback"></script>

1
2
3
4
5

# iframe + form

jsonp 只能发送 get 请求,如果要发送 post 请求:

  const iframe = document.createElement('iframe')
  iframe.name = 'name'
  iframe.style.display = 'none'
  iframe.addEventListener('load', function () {
    console.log('post success')
  })
  document.body.appendChild(iframe)

  const form = document.createElement('form')
  const input = document.createElement('input')
  form.action = url // url 为要请求的链接
  form.target = iframe.name // 在指定的 iframe 中执行 form
  form.method = 'POST'
  for (let name in data) { // data 为要上传的数据
    input.name = name
    input.value = data[name]
    form.appendChild(input.cloneNode())
  }
  form.style.display = 'none'
  document.body.appendChild(form)

  form.submit()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22

# CORS

CORS 是 W3C 标准,全称 Cross-origin resource sharing(跨域资源共享)。CORS有两种请求,简单请求(simple request)和非简单请求(not-so-simple request)。

只要同时满足以下两个条件,就属于简单请求。

  1. 请求方法时以下三种方法之一
  • HEAD
  • GET
  • POST
  1. HTTP 的头信息不超出以下几种字段
  • Accept
  • Accept-Language
  • Content-Language
  • Last-Event-ID
  • Content-Type 仅限于 application/x-www-form-urlencoded、multipart/form-data、text/plain

# 简单请求

只需要后端设置:

  res.header('Access-Control-Allow-Origin', '*')
1

# 非简单请求

非简单请求会发出一次预检测请求,返回码是204。预检测通过才会真正发出请求,这才返回200。

  res.header('Access-Control-Allow-Origin', '*');
  res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE'); // 允许的方法
  res.header('Access-Control-Allow-Headers', 'Content-Type'); // 允许的浏览器额外发送的头
1
2
3

# 代理

可以用 nginx 正向代理进行跨域。(正向代理就是 A 无法直接请求 C,但是可以请求 B,让 B 帮忙去请求 C,即代理服务器他是由你配置为你服务,去请求目标服务器地址;反向代理则是代理服务器为目标服务器服务的。比如去请求百度的域名,只需要请求 baidu.com 到代理服务器,具体内部的服务器节点我们是不知道的。)

# 其他访问 dom 的限制

# document.domain

主域名相同,但子域名不同,可以给子域名不同的页面指定 document.domain = "主域名"。就可以访问到对方的 window 对象了

# postMessage + iframe

window.postMessage() 是HTML5的一个接口,专注实现不同窗口不同页面的跨域通讯。

  // A 页面 xxx.com/#/A
  <iframe name="iframe1" src="http://xxx.com:1111"></iframe>
  <script>
    window.addEventListener('message', (e) => {
      // 一定要对来源做校验
      if (e.origin === 'http://xxx.com:1111') {
        console.log(e.data) // B 页面的数据
      }
    })

    postMessage () {
      const iframe1 = window.frames['iframe1']
      iframe1.postMessage('我是 A 页面,做一些操作')
    }
  </script>
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
  // B 页面 xxx.com/#/B
  window.addEventListener('message', (e) => {
    // 一定要对来源做校验
    if (e.origin === 'http://xxx.com:1111') {
      console.log(e.data) // A 页面的数据
      e.source.postMessage(`我是 B 页面,完成某些操作'}`, e.origin)
    }
  })
1
2
3
4
5
6
7
8

# canvas 操作图片的跨域问题

canvasdrawImage() API绘制图片会出现跨域问题,如下:

  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')

  const img = new Image()
  img.onload = function () {
    context.drawImage(this, 0, 0)
    context.getImageData(0, 0, this.width, this.height)
  };
  img.src = 'http://xxx.com/xxx.jpg'
1
2
3
4
5
6
7
8
9

这样则 chrome 会报错:

Uncaught DOMException: Failed to execute ‘getImageData’ on ‘CanvasRenderingContext2D’: The canvas has been tainted by cross-origin data.

如果使用的是 canvas.toDataURL() 方法,则会报:

Failed to execute ‘toDataURL’ on ’HTMLCanvasElement’: Tainted canvased may not be exported

都是因为跨域的问题,需要加一行 img.crossOrigin = ''

  const img = new Image()
  img.crossOrigin = '' // 或 img.crossOrigin = 'anonymous',只要不是 use-credentials,全部都会解析成 anonymous
1
2

但是该方法在 IE10 一下会报错。要想兼容低版本 IE,可以请求图片的时候不使用 new Image(),而是借助 ajaxURL.createObjectURL():

  const canvas = document.createElement('canvas')
  const context = canvas.getContext('2d')
  const xhr = new XMLHttpRequest()
  xhr.onload = function () {
    const url = URL.createObjectURL(this.response)
    const img = new Image()
    img.onload = function () {
      context.drawImage(this, 0, 0)
      context.getImageData(0, 0, this.width, this.height)
      // 图片用完后释放内存
      URL.revokeObjectURL(url)
    }
    img.src = url
  }
  xhr.open('GET', url, true)
  xhr.responseType = 'blob'
  xhr.send()
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
最后更新时间: 8/15/2021, 4:00:54 PM