关于跨域,你需要知道的

avatar
前端开发工程师 @豌豆公主

前言:跨域对于大多数开发人员并不陌生,但是当我们遇到跨域的时候,大部分前端开发者都会说出一种解决方案:让服务端设置一下参数就行了。那为什么服务端设置了这些参数就能解决跨域问题呢?除了服务端解决,还有没有其他的解决方案呢?本文会具体分析一下产生跨域的原因以及其他的解决跨域的方案。

一、为什么会跨域

1、什么是跨域
假如有一个页面A(url:https://www.a.com),页面中有一个接口请求:https://www.b.com/getData;当我们在浏览器中发送这个请求的时候,浏览器就会报错,错误信息大概如下:

Access to XMLHttpRequest at 'https://www.b.com/' from origin 'https://www.a.com' has been blocked by CORS policy: Response to preflight request doesn't pass access control check: No 'Access-Control-Allow-Origin' header is present on the requested resource.
看到这个错误大家就知道了产生了跨域的问题。
2、跨域的原因
知道了什么是跨域,那为什么会产生跨域的问题呢?
浏览器为了保护用户的信息安全提出了同源策略(注:这里需要特别注意一下是在浏览器中才会有同源策略的限制):

  • 协议相同
  • 域名相同
  • 端口相同 只有同时满足以上三个条件,我们就说两个url是同源的;所以,非同源之间就会产生跨域的问题。那既然知道了跨域产生的原因,接下来我们看一下几种常用的解决跨域的方案。当然,也有其他的方案,这里就不多说了,大家可以自行查询一下。

二、解决跨域的几种方案

1、JSONP
jsonp全称是json with padding,这是一种比较"古老"的解决跨域的方案,在那个年代,对于一些简单的get请求,我们可以通过这种方式解决;但是其他的一些复杂的请求(比如post),就需要用一些现代的方法解决了,那为什么jsonp不能发post请求呢?这要看一下jsonp解决跨域的原理:

虽然浏览器有同源策略的限制,但是script标签却不受同源策略的限制,也就是通过script我们可以向不同的源发出请求。
由于script通过src发出的是get请求,所以jsonp的方式不不能解决post请求的跨域的。

// 客户端代码
function doJSONPRequestClickHandler() {
    let script = document.createElement('script');
    script.src = 'http://www.b.com/jsonp?callback=handleCallback';
    document.body.appendChild(script);
}
// 回调
let handleCallback = function(res) {
    console.log(res);
}

jsonp最关键的点就是http://www.b.com/jsonp?callback=handleCallback后面的callback参数,服务端接收到请求之后要处理这个参数:

// 服务端用nodejs
app.get('/jsonp', (req, res) => {
    let cb = req.query.callback;
    res.send(cb + '("jsonp跨域成功")');
});

当浏览器接收到服务端的响应也就是成功加载了js,就会立即执行handleCallback('jsonp跨域成功');
以上就是jsonp跨域的原理。
2、代理
使用代理的方式解决跨域的原理也很好理解:同源策略只是在浏览器中限制的,离开了浏览器环境是不受同源策略的限制的,所以通过代理的方式,将我们的接口请求先发到同源的代理,然后再通过代理服务器转发到目的服务器。
下面是通过nodejs中间件代理的方式解决跨域:

const http = require('http');
const httpProxy = require('http-proxy');

const proxy = httpProxy.createProxyServer();

http.createServer((request, response) => {
    proxy.web(request, response, {
        target: 'https://www.b.com',
        changeOrigin: true,
    });
    proxy.on('error', err => {
        console.log(err);
    });
}).listen(8081);

3、CORS
文章刚开始讲到的“让服务端设置一下参数”就是通过CORS的方式解决跨域的,那么什么是CORS呢?
CORS就是跨源资源共享,虽然浏览器有同源策略的限制,但是浏览器给我们开了一个“后门”,浏览器是允许发送跨域请求的,但是需要服务端配合在响应头中设置一些特定的response header:

  • Access-Control-Allow-Origin: | *
  • Access-Control-Allow-Methods: [, ]*
  • Access-Control-Allow-Headers: [, ]*
  • Access-Control-Allow-Credentials: true
  • Access-Control-Allow-Origin
    这个值是告诉客户端允许的源是哪些,可以设置为特定的一个源或者*,当设置为的时候允许所有的源跨域请求,这个设置为的时候要非常小心;
  • Access-Control-Allow-Methods
    它告诉客户端允许跨域请求的方法是什么,比如GET,POST,PUT等,可以设置多个
  • Access-Control-Allow-Headers
    如果请求头中有一些自定义的请求头,就需要设置这个属性了,可以设置多个
  • Access-Control-Allow-Credentials 当我们的请求中需要带一些cookie信息的时候,需要设置它的值为true,需要注意一点,如果要使用这个属性,那么 Access-Control-Allow-Origin的值不能设置为*。
    CORS的设置如下:
app.post('/saveData', (req,res) => {
    res.header('Access-Control-Allow-Origin', '*');
    res.header('Access-Control-Allow-Methods', 'POST');
    res.header('Access-Control-Allow-Headers', 'custom');
    console.log(req.body.name);
    res.send('接到了数据' + req.body.name);
});

3、总结

以上介绍了什么是跨域,以及产生跨域的原因;产生跨域时的几种解决方案:jsonp、代理、cors...;通过以上的介绍,之后我们再遇到跨域的问题的时候不再只会说“让服务端设置一下参数就行了”,能够明白为什么要服务端设置。 对于其他的一些解决跨域的方案,本文中没有提及,大家感兴趣可以自己研究一下,比如通过websocket、iframe等方式。