跨域

92 阅读4分钟

同源策略

协议 域名 端口都一样才是同域

受制于浏览器的同源策略,页面行为有以下限制:

不同域下

  • cookie LocalStorage 不可以互相访问,否则会有安全问题

  • ajax发请求时也不支持跨域

  • DOM元素也有同源策略,iframe里如果是不同域的话,不可以随意获取里面DOM元素信息

但跨域通信有时又是一个常规需求,例如前段独立部署在一台nginx上,域名是app.xxx.com,而后端接口部在另一台服务器,域名是api.xxx.com

这种情况下,就需要跨域通信了

跨域方法:

1、jsonp 较弱

2、cors 纯后端提供

3、postMessage两个页面之间通信

不常用方式:

4、子域和父域之间通信:document.domain

5、window.name

6、location.hash

7、http-proxy

8、nginx

9、websocket

jsonp

因为img link script是可以引用不同域下的文件的,不受同源策略限制,例如我们引cdn的文件的时候就是这种情况

这种方式通常是通过script里加载一个方法调用的表达式,这个方法已经预先定义好,我们可以在回调中拿到调用时传入的数据做一些处理

缺点:只有get请求,post delete put都不可以,因此get请求的一些限制,例如长度限制,它都有

安全性较差 ****会有xss攻击的风险 ****很少采用

cors

如果有两个服务分别监听3000 4000端口,当3000端口的页面发起一个请求4000端口提供的api时,如果不做任何处理,就会报:

Failed to load http://localhost:4000/getData: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘http://localhost:3000’ is therefore not allowed access.

但其实,监听4000端口的服务器能收到发来的请求(经测试发现服务器对该请求会正常处理,跨域过来的请求对应的request和response两个对象和普通请求过来没有什么区别,也可以返给浏览器,但浏览器禁止掉了这个请求),通过origin,在封装好的Request对象中,可以拿到headers.origin属性,这样就可以进一步通过判断这个属性确定是否返回结果或返回什么样的结果

如果前端发请求时添加了自定义的头,那后端也需要给Response对象配置Access-Control-Allow-Headers,用以表示可以访问的header有哪些

let express = require('express');
let app = express();
let whitList = ['http://localhost:3000']

app.use(function (req,res,next) {
  let origin = req.headers.origin;
  if(whitList.includes(origin)){
    // 设置哪个源可以访问我
    res.setHeader('Access-Control-Allow-Origin', origin);
    // 允许携带哪个头访问我
    res.setHeader('Access-Control-Allow-Headers','name');
    // 允许哪个方法访问我
    res.setHeader('Access-Control-Allow-Methods','PUT');
    // 允许携带cookie
    res.setHeader('Access-Control-Allow-Credentials', true);
    // 预检的存活时间
    res.setHeader('Access-Control-Max-Age',6);
    // 允许返回的头
    res.setHeader('Access-Control-Expose-Headers', 'name');

    if(req.method === 'OPTIONS'){
      res.end(); // OPTIONS请求不做任何处理
    }
  }
  next();
});

特点:对于复杂请求,要先发一个预捡请求(OPTIONS),预捡请求不是一直发,可以通过Access-Control-Max-Age设置预捡请求的存活时间

如果在3000端口下页面的js中往cookie里写了一些内容:

let xhr = new XMLHttpRequest;
document.cookie = 'name=zfpx';

再向4000端口发起一个请求时,cookie是不会被发过去的,相应的,4000端口监听的服务,也是访问不到cookie的,如果希望不同域之间的cookie能够访问到,则客户端xhr对象需要添加:

xhr.withCredentials = true;

服务端需要设置

res.setHeader('Access-Control-Allow-Credentials', true);

当服务器加了一些自定义的头,浏览器希望能获取到时,需要设置

res.setHeader('Access-Control-Expose-Headers', 'name');

如果设置了Access-Control-Allow-Origin: *,则不可以和Access-Control-Allow-Credentials: true共同存在

2023年01月16日10:49:07补充:

在阅读了developer.chrome.com/blog/inside… 这篇文章后,进一步确定了同源策略检查的时机:

浏览器主进程(UI进程)的网络线程收到响应后,进行安全性检查和跨域检查,如果检查不通过,将不会将内容交给该tab对应的Renderer进程去渲染

postMessage

两个页面之间通信:

a.html:

<body>
  <iframe src="http://localhost:4000/b.html" frameborder="0" id="frame" onload="load()"></iframe>
  <script>
    function load() {
      let frame = document.getElementById('frame');
      frame.contentWindow.postMessage('我爱你','http://localhost:4000');
      window.onmessage = function (e) {
        console.log(e.data);
      }
    }
  </script>
</body>

b.html:

<body>
  <script>
    window.onmessage = function (e) {
      console.log(e.data);
      e.source.postMessage('我不爱你',e.origin)
    }
  </script>
</body>

nginx

如果有一个node服务,启了3000端口,管理了如下静态文件:

    let xhr = new XMLHttpRequest;
    xhr.open('get','http://localhost/a.json',true);
    xhr.onreadystatechange = function () {
      if(xhr.readyState === 4){
        if(xhr.status>=200 && xhr.status < 300 || xhr.status ===304){
          console.log(xhr.response);
          console.log(xhr.getResponseHeader('name'));
        }
      }
    }

    xhr.send();

向localhost发起的请求是nginx监听的80,如果不做任何处理,nginx会返回405,错误信息为:

Failed to load http://localhost/a.json: No ‘Access-Control-Allow-Origin’ header is present on the requested resource. Origin ‘[http://localhost:3000’](http://xn--localhost:3000-ec3h) is therefore not allowed access.

这个错误和上面的cors跨域出现的问题是完全一致的,所以只需要通过add_header添加响应的头即可:

location ~.*\.json {
    root json;
    add_header “Access-Control-Allow-Origin” “*”;
}

参考资料:MDN跨源资源访问