10种跨域方法

123 阅读3分钟

为什么要跨域

跨域问题是由浏览器的同源策略所导致的。

何为同源?

protocol(协议)、domain(域名)、port(端口)三者一致。

如何解决跨域

1.JSONP

JSONP利用了script img link等标签没有跨域限制的特性,只支持GET方法。

流程:

1.前端定义全局cb

2.加在请求后面,声明执行函数callback=cb

3.后端获取cb,带上参数并以执行函数的方式传回前端

//后端
const Koa = require("koa");
const fs = require("fs");
const app = new Koa();

app.use(async (ctx, next) => {
  if (ctx.path === "/api/jsonp") {
    const { cb, msg } = ctx.query;
    ctx.body = `${cb}(${JSON.stringify({ msg })})`;
    return;
  }
});

app.listen(8080);

//前端
<script type="text/javascript">
  window.cb = function(res) {
    console.log(res);
  };
</script>
<script type="text/javascript"
    src="http://localhost:8080/api/jsonp?msg=hello&callback=cb">
</script>

//AJAX版本,实际上就是自动创建一个全局cb,即success的function
<script>
  $.ajax({
    url: "http://localhost:8080/api/jsonp",
    dataType: "jsonp",
    type: "get",
    data: {
      msg: "hello"
    },
    jsonp: "cb",
    success: function(data) {
      console.log(data);
    }
  });
</script>

2.CORS

使用额外的HTTP头,向不同源的服务器发起请求。

目前的HTTP请求一般在设置了一些请求头之后都是非简单请求,会触发OPTION 预检

需要服务器配合。

app.use(async (ctx, next) => {
  ctx.set("Access-Control-Allow-Origin", ctx.headers.origin);
  ctx.set("Access-Control-Allow-Credentials", true);
  ctx.set("Access-Control-Request-Method", "PUT,POST,GET,DELETE,OPTIONS");
  ctx.set(
    "Access-Control-Allow-Headers",
    "Origin, X-Requested-With, Content-Type, Accept, cc"
  );
  if (ctx.method === "OPTIONS") {
    ctx.status = 204;
    return;
  }
  await next();
});

cookie的使用tip: 需要在头部加上withCredentials=true,且服务端Access-Control-Allow-Origin非*

一般情况下后端接口是不会开启这个跨域头的,除非是一些与用户无关的不太重要的接口。

3.HTTP Proxy

最常用的就是webpack-dev-server(使用了http-proxy库)

客户端代理, 代理客户端, 服务端不知道实际发起请求的客户端。

利用了服务器请求服务器不存在跨域,相当于模拟了一个nginx服务请求

//webpack.config.js
module.exports = {
  //其他配置
  devServer: {
    port: 8000,
    proxy: {
      "/api": {
        target: "http://localhost:8080"
      }
    }
  },
};

//前端请求方式,不带域名,现在是同域请求
axios.get("/api/getSomething").then(res => {
  console.log(res.data);
});

4.Nginx反向代理

反向代理即是服务端代理, 代理服务端, 客户端不知道实际提供服务的服务端。

与前端无关,需要配置nginx

www.aaa.cn -> www.aaa.com // 从aaa.cn源来,进行反向代理
//proxy服务器
server {
    listen          80;
    server_name     www.aaa.com; //当前服务器
    location / {
        proxy_pass  www.aaa.cn; //反向代理
        proxy_cookie_domain   www.aaa.cn  www.aaa.com;
        add_header  Access-Control-Allow-Orgin  www.aaa.cn;
        add_header  Access-Control-Allow-Credentials true
    }
}

5.Websocket

H5的一套规范,让Client和Server之间进行持久连接,没有跨域限制。

//前端部分
  let socket = new WebSocket("ws://localhost:8080");
  socket.onopen = function() {
    socket.send("socket建立了,发送这条消息");
  };
  socket.onmessage = function(e) {
    console.log('收到服务器给我的消息了', e);
  };
  
 //后段部分
const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 8080 });
server.on("connection", function(socket) {
  socket.on("message", function(data) {
    socket.send(data);
  });
});

以下大都是多窗口之间的数据传输

6.window.postMessage

a.html

<iframe
  src="http://localhost:8080"
  frameborder="0"
  id="iframe"
  onload="load()"
></iframe>
<script>
  function load() {
    iframe.contentWindow.postMessage("从a.html发出的消息", "http://localhost:8080");
    window.onmessage = e => {
      console.log(e.data);
    };
  }
</script>

b.html

<script>
  window.onmessage = e => {
    console.log(e.data); // 从a.html发出的消息
    e.source.postMessage(e.data, e.origin);
  };
</script>

7.document.domain + iframe

只用于二级域名相同情况,都加上document.domain ='second.com'即可获取iframe.contentWindow下的全局变量

8.window.location.hash + iframe

通过 url 带 hash ,通过一个非跨域的中间页面来传递数据。

9.window.name + iframe

location变化,重新加载后,它的name属性可以依然保持不变。

10.修改本地HOST或浏览器开启跨域