关于跨域我所知道的

88 阅读4分钟

什么是跨域?跨过浏览器的同源策略去请求资源。

同源策略:协议,域名,端口都相同就是同源,是出于安全的考虑。

JSONP

JSONP 只支持 GET 请求,但是可以兼容老式浏览器。
利用了 script 标签的 src 属性没有跨域限制的漏洞。

  1. 创建标签,src 地址指向跨域请求数据的服务器
  2. 提供一个回调函数来接受数据,函数名可以通过地址参数传递进行约定
  3. 服务器收到请求后,返回一个包装了 JSON 数据的响应字符串,类似这样:callback({...})
function jsonp({ url, params, callback }) {
  return new Promise((resolve, reject) => {
    // 创建一个临时的 script 标签用于发起请求
    const script = document.createElement('script');
    // 将回调函数临时绑定到 window 对象,回调函数执行完成后,移除 script 标签
    window[callback] = data => {
      resolve(data);
      document.body.removeChild(script);
    };
    // 构造 GET 请求参数,key=value&callback=callback
    const formatParams = { ...params, callback };
    const requestParams = Object.keys(formatParams)
      .reduce((acc, cur) => {
        return acc.concat([`${cur}=${formatParams[cur]}`]);
      }, [])
      .join('&');
    // 构造 GET 请求的 url 地址
    const src = `${url}?${requestParams}`;
    script.setAttribute('src', src);
    document.body.appendChild(script);
  });
}

// 调用时
jsonp({
  url: 'https://xxx.xxx',
  params: { ...},
  callback: 'func',
})

// 本质
// <script src='https://xxx.xxx.xx?key=value&callback=xxx'><script>

CORS

CORS 支持所有的 HTTP 请求,是跨域最主流的方案。

使用额外的 HTTP 头来告诉浏览器,让运行在某一个 origin 上的 Web 应用允许访问来自不同源服务器上的指定的资源。

CORS 需要浏览器和服务器同时支持,整个 CORS 的通信过程,都是浏览器自动完成。

怎么个自动法?

简单来说,浏览器一旦发现请求是一个跨域请求,首先会判断请求的类型,如果是简单请求,会在请求头中增加一个 Origin 字段,表示这次请求是来自哪一个源。而服务器接受到请求后,会返回一个响应,响应头中会包含一个叫 Access-Control-Allow-Origin 的字段,它的值要么包含由 Origin 首部字段所指明的域名,要么是一个 "*",表示接受任意域名的请求。如果响应头中没有这个字段,就说明当前源不在服务器的许可范围内,浏览器就会报错。

如果是非简单请求,会在正式通信之前,发送一个预检请求(preflight),目的在于询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些 HTTP 动词和头信息字段,只有得到肯定答复,浏览器才会发出正式的请求,否则就报错。

你可能发现我们在日常的开发中,会看到很多使用 OPTION 方法发起的请求,它其实就是一个预检请求。

PostMessage

实现跨文档通信,更多地用于窗口通信。
此外,除了实现窗口通信,postMessage 还可以同 Web Worker 和 Service Work 进行通信。

window.postMessage(message, origin, [transfer])

window.addEventListener("message", function receiveMessage(event) {}, false);

Websocket

双工通信。在建立连接之后,server 与 client 都能主动向对方发送或接收数据。

Nginx 反向代理

利用了服务器对服务器没有同源策略限制。

# nginx.config
# ...
server {
  listen       80;
  server_name  www.domain1.com;
  location / {
    proxy_pass   http://www.domain2.com:8080;  #反向代理
    proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
    index  index.html index.htm;

    # 当用 webpack-dev-server 等中间件代理接口访问 nignx 时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
    add_header Access-Control-Allow-Origin *;
    add_header Access-Control-Allow-Credentials true;
    # ...
  }
}

Node 中间件代理

利用了服务器对服务器没有同源策略限制。
使用了 Node 中间件做为代理。需要注意的是,浏览器向代理服务器请求时仍然遵循同源策略,别忘了在 Node 层通过 CORS 做跨域处理:

const https = require('https')
// 接受客户端请求
const sever = https.createServer((req, res) => {
  ...
  const { method, headers } = req
  // 设置 CORS 允许跨域
  res.writeHead(200, {
    'Access-Control-Allow-Origin': '*',
    'Access-Control-Allow-Methods': '*',
    'Access-Control-Allow-Headers': 'Content-Type',
    ...
  })
  // 请求服务器
  const proxy = https.request({ host: 'xxx', method, headers, ...}, response => {
    let body = ''
    response.on('data', chunk => { body = body + chunk })
    response.on('end', () => {
      // 响应结果转发给客户端
      res.end(body)
    })
  })
  // 结束请求
  proxy.end()
})

document.domain

二级域名相同的情况下,设置 document.domain 就可以实现跨域。

document.domain = 'test.com' // 设置 domain 相同

// 通过 iframe 嵌入跨域的页面
const iframe = document.createElement('iframe')
iframe.setAttribute('src', 'b.test.com/xxx.html')
iframe.onload = function() {
  // 拿到 iframe 实例后就可以直接访问 iframe 中的数据
  console.log(iframe.contentWindow.xxx)
}
document.appendChild(iframe)