Skip to content

跨域解决方案

为什么出现跨域,因为浏览器的同源策略

同源策略

浏览器的安全策略,限制了来自不同源的脚本对当前文件的访问权限

同源:同一协议(protocol)、域名(domain)、端口(port)

同源策略可以防止跨站脚本(XSS)攻击等安全问题

跨域解决方案

共十种,JSONP、CORS、window.postmessage、websocket、nginx 代理、node代理、document.domain + iframe、document.location.hash + iframe、window.name + iframe、修改浏览器安全配置

后四种不常见,问的最多的也就是 CORS、window.postmessgae、websocket、JSONP 等

面试时问的最多的问题如:CORS 是什么,有什么用,怎么用,JSONP 的原理,window.postmessage 是什么,websocket 是什么

JSONP

  • 最古老的彼岸者,利用了 script 标签没有跨域限制这个特点
  • 仅支持 GET 方法
  • 步骤
    • 定义jsonp回调函数方法jsonpCallback
    • script 请求接口(后端)时带上cb=jsonpCallback 参数,如 /api?callback=jsonpCallback
    • 后端 response 返回 jsonpCallback ({a: 'b'}),前端执行 jsonpCallback ,就拿到注入的数据

CORS(跨域资源共享)

跨域资源共享(Cross-Origin Resource Sharing,简写为CORS)简称跨域访问,是HTML5提供的标准跨域解决方案

服务端/后端在相应头中添加 Access-Control-Allow-* 头,告知浏览器端通过此请求只需要服务端/后端支持即可,不涉及前端改动

具体实现方法

CORS 将请求分为简单请求(Simple Requests)和需预检请求(Preflighted requests),不同场景有不同行为:

简单请求

不会触发预检请求的称为简单请求。当请求满足以下条件时就是一个简单请求

  • 请求方式:HEAD、GET、POST
  • 请求头:Accept、Accept-Language、Content-Language、Content-Type
    • Content-Type 仅支持:application/x-www-form-urlencodedmultipart/form-datatext/plain
需预检请求

当一个请求不满足以上简单请求的条件时,浏览器会自动向服务器发送一个 OPTIONS 请求,通过服务端返回的 Access-Control-Allow-* 判定请求是否被允许。

CORS 引入了以下几个以 Access-Control-Allow-* 开头:

  • Access-Control-Allow-Origin 表示允许的来源
  • Access-Control-Allow-Methods 表示允许的请求方法
  • Access-Control-Allow-Headers 表示允许的请求头
  • Access-Control-Allow-Credentials 表示允许携带认证信息

当请求符合响应头的这些条件时,浏览器才会发送并响应正式的请求。

window.postMessage

window.postMessage() 方法可以安全地实现跨源通信。通常,对于两个不同页面的脚本,只有当执行它们的页面位于具有相同的协议(通常为 https),端口号(443 为 https 的默认值),以及主机 (两个页面的模数 Document.domain设置为相同的值) 时,这两个脚本才能相互通信。window.postMessage() 方法提供了一种受控机制来规避此限制,只要正确的使用,这种方法就很安全。

用途

  1. 页面和其打开的新窗口的数据传递
  2. 多窗口之间消息传递
  3. 页面与嵌套 iframe 消息传递

用法

index.html

html
<iframe
  src="http://localhost:8080"
  frameborder="0"
  id="iframe"
  onload="load()"
></iframe>
<script>
  function load() {
    iframe.contentWindow.postMessage("五年前端三年面试", "http://localhost:8080");
    window.onmessage = e => {
      console.log(e.data);
    };
  }
</script>

another.html

html
<div>hello</div>
<script>
  window.onmessage = e => {
    console.log(e.data); // 五年前端三年面试
    e.source.postMessage(e.data, e.origin);
  };
</script>

WebSocket

双向数据通信。客户端和服务器之间存在持久的链接,而且双方都可以随时发送数据

这种方法的本质是没有使用 HTTP,因此也没有跨域的限制

前端:

html
<script>
  let socket = new WebSocket("ws://localhost:8080");
  socket.onopen = function() {
    socket.send("五年前端三年面试");
  };
  socket.onmessage = function(e) {
    console.log(e.data);
  };
</script>

后端:

javascript
const WebSocket = require("ws");
const server = new WebSocket.Server({ port: 8080 });
server.on("connection", function(socket) {
  socket.on("message", function(data) {
    socket.send(data);
  });
});

Nginx 反向代理

请求 /api,转发到 http://localhost:8080

请求/api1,转发到http://localhost:8090

nginx
server {
        listen 80;
        location /api {
            proxy_pass http://localhost:8080;
        }
        location /api1 {
            proxy_pass http://localhost:8090;
        }
}

Node 代理

和 Nginx 代理不同的是,Nginx 代理是请求接口在服务端转发;

Node 代理是同域请求来规避

document.domain + iframe

该方法只能用于二级域名相同的情况下,比如a.azhubaby.comb.azhubaby.com 适用于该方法。只需要给页面添加document.domain ='azhubaby.com' 表示二级域名都相同就可以实现跨域

www.azhubabycom.
三级域二级域名顶级域根域

a.azhubaby.com

html
<body>
  hello, world
  <iframe
    src="http://b.azhubaby.com/b.html"
    frameborder="0"
    onload="load()"
    id="frame"
  ></iframe>
  <script>
    document.domain = "azhubaby.com";
    function load() {
      console.log(frame.contentWindow.a);
    }
  </script>
</body>

b.azhubaby.com

html
<body>
  hellob
  <script>
    document.domain = "azhubaby.com";
    var a = 100;
  </script>
</body>

document.location.hash + iframe

实现原理

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

window.name + iframe

window 对象的 name 属性是一个很特别的属性,当该 window 的 location 变化,然后重新加载,它的 name 属性可以依然保持不变

修改浏览器安全配置

参考资料