几种常见跨域解决方案

一、同源策略

现在很多的项目开发,都实现了前后端分离开发,能够大大提高效率,但是导致前端向后端发送请求,会出现跨域错误。
先说说什么是跨域当访问一个网站时,一个完整的域名由传输协议、网络名、域名主体和域名后缀四个部分组成,如下图所示:
在这里插入图片描述
相同后缀的域名主体是不能重复。http://www.baidu.com和http://www.google.cn就是两个不同的域。跨域就是在当前域去访问其他域的资源。就比如我们在自己的机器上通过ajax去调用百度地图提供的API接口数据,这就是跨域请求。

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Demo</title>
</head>
<body>
    <div id="div1">
        <button id="btn">请求</button>
    </div>
</body>
<script type="text/javascript">
    window.onload = function() {

    var oBtn = document.getElementById('btn');

    oBtn.onclick = function() {

        var xhr = new XMLHttpRequest();

        xhr.onreadystatechange = function() {
            if (xhr.readyState == 4 && xhr.status == 200) {
                    alert( xhr.responseText );
            }
        };
        xhr.open('get', 'http://api.map.baidu.com/getscript?v=2.0&ak=Kpjp7jddqVUhWK5VkrfNt3YNezY88NtR&services=&t=20170517145936', true);
        xhr.send(); 
    };

};
</script>
</html>

在这里插入图片描述
但是由于浏览器安全策略的原因,这样的请求是行不通的。这种安全策略就是同源策略。
同源策略(Same Origin Policy,SOP)是一种约定,是由Netscape提出的一个著名的安全策略。现在所有支持JavaScript 的浏览器都会使用这个策略。它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,则浏览器的正常功能可能都会受到影响,浏览器很容易受到XSS、CSFR等攻击。
这里的源指的就是:域名,协议,端口。同源就是域名,协议,端口相同。所以同源策略就是出于安全机制的考虑,当前域只能访问当前域的数据,不能访问其他域的数据。
是否同源示例:
在这里插入图片描述

同源策略确实提高了浏览器的安全性,但是正所谓有利就有弊。在提高了安全的同时就限制了Web拓展上的灵活性。
先来说说同源策略限制了以下几种行为:

  1. Cookie、LocalStorage 和 IndexDB 无法读取
  2. DOM 和 Js对象无法获取
  3. AJAX 请求不能发送
    但是也有不受同源策略限制的:
    1、页面中的链接,重定向以及表单提交是不会受到同源策略限制的。
    2、跨域资源的引入是可以的。但是js不能读写加载的内容。如嵌入到页面中的<script src="..."></script>,<img>,<link>,<iframe>等 。

二、跨域解决方案

但是问题就是用来解决的,既然有了同源策略的安全限制,那么就会有跨域请求的解决方案。
接下来就说一说我所了解的跨越的解决方案。

1、JSONP

JSONP 是 JSON with padding(填充式 JSON 或参数式 JSON)的简写,它是JavaScript设计模式中的一种代理模式。前面提到的不受同源策略限制中的第二点:在html页面中通过相应的标签从不同域名下加载静态资源文件是
被浏览器允许的,所以我们可以通过这个机制来进行跨域。简单的说,就是动态创建

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>JSONP实现跨域请求</title>
</head>
<body>
    <div id="div1">
        <button id="btn">请求</button>
    </div>
</body>
<script type="text/javascript">
	//控制台输出返回的数据
    function printout(response){
            console.log(response);
    }
</script>
<script type="text/javascript">
    window.onload = function() {
    var oBtn = document.getElementById('btn');
    oBtn.onclick = function() {     
        var script = document.createElement("script");
        script.src = "http://api.map.baidu.com/getscript?v=2.0&ak=5GrRgGEU6ZtFldAGQdRMd4PwjkYlURUB";
        document.body.insertBefore(script, document.body.firstChild);   
    };
};
</script>
</html>

在这里插入图片描述
虽然没有成功的调用接口,但是能够返回提示信息说明已经能够成功请求了,上面的这个问题是秘钥有问题,我明明是自己注册的一个没要但是还是没用,我尝试解决了下也还没能解决,
后面再继续研究一下。成功解决了,会再出一个文章来仔细说说如何调用百度地图的API。如果有知道什么原因的小伙伴,欢迎交流。
虽然JSONP使用起来方便,但是也存在一些问题:
首先, JSONP 是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码。另外JSONP只支持GET请求。

2、跨域资源共享 CORS

CORS是一个W3C标准,全称是"跨域资源共享"(Cross-origin resource sharing)。
它允许浏览器向跨源服务器,发出 XMLHttpRequest 请求,从而克服了 AJAX 只能同源使用的限制,使用AJAX技术跨域获取数据。
SERVER端支持CORS,增加对Origin等字段判断,返回响应是增加Access-Control-Allow-Origin、Access-Control-Allow-Method等字段。
CORS需要浏览器和服务器同时支持。目前,所有浏览器都支持该功能,IE浏览器不能低于IE10。IE8+:IE8/9需要使用XDomainRequest对象来支持CORS。
整个CORS通信过程,都是浏览器自动完成,不需要用户参与。对于开发者来说CORS通信与同源的AJAX通信没有差别,代码完全一样。浏览器一旦发现AJAX请求跨源,就会自动添加一些附加的头信息,有时还会多出一次附加的请求,但用户不会有感觉。因此,实现CORS通信的关键是服务器。只要服务器实现了CORS接口,就可以跨源通信。
浏览器将CORS请求分成两类:简单请求(simple request)和预检请求(复杂请求)。
同时满足以下条件,那么就是简单请求:
1、请求方法是以下三种方法之一:
HEAD
GET
POST
2、HTTP的头信息不超出以下几种字段:
Accept
Accept-Language
Content-Language
Last-Event-ID
Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain
3、没有事件监听器被注册到任何用来发出请求的 XMLHttpRequestUpload 上(经由 XMLHttpRequest.upload 方法取得)上。
4、请求中没有 ReadableStream 类型的内容被用于上传。
浏览器发现自己发送的是简单跨域请求,则会只发送一次HTTP请求。相较于同源请求,CORS简单请求会在头信息中额外增加一个Origin字段。
假如从www.baidu.com去请求www.google.com的资源:
请求:

GET /resources/public-data/ HTTP/1.1
Host: www.google.com
User-Agent: Mozilla/5.0 (Macintosh; U; Intel Mac OS X 10.5; en-US; rv:1.9.1b3pre) Gecko/20081130 Minefield/3.1b3pre
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Accept-Charset: utf-8;q=0.7,*;q=0.7
Connection: keep-alive
Origin: www.baidu.com

响应:

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2.0.61 
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

HTTP请求中的Origin字段表示该请求是来自于www.baidu.com的请求
如果Origin指定的域名在许可范围内,服务器返回的响应,会多出几个头信息字段。

Access-Control-Allow-Origin: www.baidu.com
Access-Control-Allow-Credentials: true
Access-Control-Expose-Headers: FooBar
Content-Type: application/xml

本次请求示例中响应是携带了Access-Control-Allow-Origin: *,或者是Access-Control-Allow-Origin: www.baidu.com。如果Origin指定的源并不在许可范围内,那么服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。但是,这种错误并不能通过状态码识别,因为HTTP回应的状态码有可能是200。
上面的头信息之中,有三个与CORS请求相关的字段,都以Access-Control-开头

Access-Control-Allow-Origin :该字段是必须的。它的值要么是请求时Origin字段的值,
要么是一个**表示接受任意域名的请求;
Access-Control-Allow-Credentials: 该字段可选。它的值是一个布尔值,表示是否允许发送
Cookie。默认情况下,Cookie不包括在CORS请求之中。设为true,即表示服务器明确许可,
Cookie可以包含在请求中,一起发给服务器。这个值也只能设为true,如果服务器不要浏览器
发送Cookie,删除该字段即可。
Access-Control-Expose-Headers:该字段可选。CORS请求时,XMLHttpRequest对象的
getResponseHeader()方法只能拿到6个基本字段:Cache-Control、Content-Language、
Content-Type、Expires、Last-Modified、Pragma。如果想拿到其他字段,就必须在
Access-Control-Expose-Headers里面指定。

不满足简单请求条件之一的请求即是预检(非简单)请求。因为预检请求的CORS请求,会在正式通信之前,增加一次HTTP查询请求,等到预检请求完成后,浏览器才会发送真正的响应,所以称为预检请求。
对于预检请求浏览器先询问服务器,当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段。只有得到肯定答复,浏览器才会发出正式的XMLHttpRequest请求,否则就报错。

var url = 'http://api.alice.com/cors';
var xhr = new XMLHttpRequest();
xhr.open('PUT', url, true);
xhr.setRequestHeader('X-Custom-Header', 'value');
xhr.send();

浏览器发现,这是一个非简单请求,就自动发出一个预检请求,要求服务器确认可以这样请求。下面是这个预检请求的HTTP头信息。

 OPTIONS /cors HTTP/1.1
   Origin: http://api.bob.com
   Access-Control-Request-Method: PUT
   Access-Control-Request-Headers: X-Custom-Header
   Host: api.alice.com
   Accept-Language: en-US
   Connection: keep-alive
   User-Agent: Mozilla/5.0...

预检请求用的请求方法是OPTIONS,表示这个请求是用来询问的。头信息里面,关键字段是Origin,表示请求来自哪个源。
除了Origin字段,预检请求的头信息包括两个特殊字段。

Access-Control-Request-Method:该字段是必须的,用来列出浏览器的CORS请求会用到哪些HTTP方法。
Access-Control-Request-Headers:该字段是一个逗号分隔的字符串,指定浏览器CORS请求会额外发送的头信息字段。

服务器收到预检请求以后,检查了Origin、Access-Control-Request-Method和Access-Control-Request-Headers字段以后,确认允许跨源请求,就可以做出回应

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2.0.61 (Unix)
Access-Control-Allow-Origin: http://api.bob.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Content-Type: text/html; charset=utf-8
Content-Encoding: gzip
Content-Length: 0
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

上面的HTTP回应中,关键的是Access-Control-Allow-Origin字段,表示http://api.bob.com可以请求数据。该字段也可以设为星号,表示同意任意跨源请求。
如果浏览器否定了"预检"请求,会返回一个正常的HTTP回应,但是没有任何CORS相关的头信息字段。这时,浏览器就会认定,服务器不同意预检请求,因此触发一个错误,被XMLHttpRequest对象的onerror回调函数捕获。
控制台会打印出如下的报错信息。
服务器回应的其他CORS相关字段如下:

Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: X-Custom-Header
Access-Control-Allow-Credentials: true
Access-Control-Max-Age: 1728000
Access-Control-Allow-Methods:该字段必需,它的值是逗号分隔的一个字符串,表明服务器支持的所有跨域请求的方法。注意,返回的是所有支持的方法,而不单是浏览器请求的那个方法。这是为了避免多次"预检"请求。
Access-Control-Allow-Headers:如果浏览器请求包括Access-Control-Request-Headers字段,则Access-Control-Allow-Headers字段是必需的。它也是一个逗号分隔的字符串,表明服务器支持的所有头信息字段,不限于浏览器在"预检"中请求的字段。
Access-Control-Allow-Credentials: 该字段与简单请求时的含义相同。
Access-Control-Max-Age: 该字段可选,用来指定本次预检请求的有效期,单位为秒。上面结果中,有效期是20天(1728000秒),即允许缓存该条回应1728000秒(即20天),在此期间,不用发出另一条预检请求。

一旦服务器通过了预检请求,以后每次浏览器正常的CORS请求,就都跟简单请求一样,会有一个Origin头信息字段。服务器的回应,也都会有一个Access-Control-Allow-Origin头信息字段。

PUT /cors HTTP/1.1
Origin: http://api.bob.com
Host: api.alice.com
X-Custom-Header: value
Accept-Language: en-US
Connection: keep-alive
User-Agent: Mozilla/5.0...

浏览器的正常CORS请求。上面头信息的Origin字段是浏览器自动添加的。下面是服务器正常的回应。

Access-Control-Allow-Origin: http://api.bob.com
Content-Type: text/html; charset=utf-8
Access-Control-Allow-Origin字段是每次回应都必定包含的

CORS与JSONP的使用目的相同,但是比JSONP更强大。JSONP只支持GET请求,CORS支持所有类型的HTTP请求。JSONP的优势在于支持老式浏览器,以及可以向不支持CORS的网站请求数据。

3、vue框架

由于现在很多的项目都是通过前后端分离进行开发,所以前端如果使用一些框架,则可以在前端开发通过配置文件设置服务器代理的方式也可以解决跨域的问题。
只需要新建一个config.js文件然后添加下面得代码:

module.exports = {
  devserver: {
    host: 'localhost', 
    port: 8080, 
    autoOpenBrowser: false,
    errorOverlay: true,
    notifyOnErrors: true,
    poll: false, 
    assetsSubDirectory: 'static',
    assetsPublicPath: '/',
    proxy: {
      ''/abc/def/api'': {
        target: 'http://localhost/abc/def:8081',//后端接口地址
        changeOrigin: true,//是否允许跨越
		ws:true, //WebSocket协议
        pathRewrite: {
          '^/abc/def/api': '/api',//重写,
        }
      }
  }
  }
}

target对应的值就是向后端服务器发送请求的主机+端口,相当于把前端发送请求的主机+端口自动替换成挂载的主机和端口,这样前后端的主机端口就都不会存在跨域问题了。而changeOrigin:true;表示是否允许跨越;这个一定要设置为true。
上面的这个方式也正式我在开发中使用的解决跨域问题的方案。

4、降域 document.domain

同源策略认为域和子域属于不同的域,如:
child1.a.com 与 a.com,
child1.a.com 与 child2.a.com,
abc.child1.a.com 与 child1.a.com
两两不同源,但是可以通过设置 document.domain=‘a.com’,浏览器就会认为它们都是同一个源。想要实现以上任意两个页面之间的通信,两个页面必须都设置documen.domain=‘a.com’。
此方式的特点:
只能在父域名与子域名之间使用,且将 xxx.child1.a.com域名设置为a.com后,不能再设置成child1.a.com
存在安全性问题,当一个站点被攻击后,另一个站点会引起安全漏洞
这种方法只适用于 Cookie 和 iframe 窗口。

5、 postMessage跨域

postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
a.) 页面和其打开的新窗口的数据传递
b.) 多窗口之间消息传递
c.) 页面与嵌套的iframe消息传递
d.) 上面三个场景的跨域数据传递

用法:postMessage(data,origin)方法接受两个参数
data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化。
origin: 协议+主机+端口号,也可以设置为"*",表示可以传递给任意窗口,如果要指定和当前窗口同源的话设置为"/"。

1.)a.html:(http://www.domain1.com/a.html)

<iframe id="iframe" src="http://www.domain2.com/b.html" style="display:none;"></iframe>
<script>       
    var iframe = document.getElementById('iframe');
    iframe.onload = function() {
        var data = {
            name: 'aym'
        };
        // 向domain2传送跨域数据
        iframe.contentWindow.postMessage(JSON.stringify(data), 'http://www.domain2.com');
    };

    // 接受domain2返回数据
    window.addEventListener('message', function(e) {
        alert('data from domain2 ---> ' + e.data);
    }, false);
</script>

2.)b.html:(http://www.domain2.com/b.html)

<script>
    // 接收domain1的数据
    window.addEventListener('message', function(e) {
        alert('data from domain1 ---> ' + e.data);

        var data = JSON.parse(e.data);
        if (data) {
            data.number = 16;

            // 处理后再发回domain1
            window.parent.postMessage(JSON.stringify(data), 'http://www.domain1.com');
        }
    }, false);
</script>

以上就是我所了解的几种解决跨域的方案,由于不是主要做前端开发的,所以内容可能有偏差和不正确的地方,如果有误,欢迎指正,另外本文参考了:
https://segmentfault.com/a/1190000011145364
https://juejin.im/post/5a2f92c65188253e2470f16d

猜你喜欢

转载自blog.csdn.net/qq_41153943/article/details/106075077