前端跨域及跨域解决方案

前后端交互时经常受跨域问题的煎熬。最近正好遇到,所以略微总结了下,仅供参考!

什么是跨域

出于安全考虑,浏览器会限制脚本中发起的跨域请求,使用 XMLHttpRequest 对象和Fetch发起 HTTP 请求就必须遵守同源策略。即:“URL的首部”指window.location.protocol +window.location.host必须相同,也可以理解为“Domains, protocols and ports must match”。

跨源资源共享标准通过新增一系列 HTTP 头,让服务器能声明哪些来源可以通过浏览器访问该服务器上的资源。另外,对那些会对服务器数据造成破坏性影响的 HTTP 请求方法(特别是 GET 以外的 HTTP 方法,或者搭配某些MIME类型的POST请求),标准强烈要求浏览器必须先以 OPTIONS 请求方式发送一个预请求(preflight request),从而获知服务器端对跨源请求所支持 HTTP 方法。在确认服务器允许该跨源请求的情况下,以实际的 HTTP 请求方法发送那个真正的请求。服务器端也可以通知客户端,是不是需要随同请求一起发送信用信息(包括 Cookies 和 HTTP 认证相关数据)。

XMLHttpRequest 对象

XMLHttpRequest 对象用于在后台与服务器交换数据。开发者可通过XMLHttpRequest对象

* 在不重新加载页面的情况下更新网页
* 在页面已加载后从服务器请求数据
* 在页面已加载后从服务器接收数据
* 在后台向服务器发送数据

访问控制场景

简单请求

所谓的简单,是指:

  • 只使用 GET, HEAD 或者 POST 请求方法。如果使用 POST 向服务器端传送数据,则数据类型(Content-
    Type)只能是 application/x-www-form-urlencoded, multipart/form-data 或 text/plain中的一种。
  • 不会使用自定义请求头(类似于 X-Modified 这种)。

预请求

不同于上面的简单请求,“预请求”要求必须先发送一个 OPTIONS 请求给目的站点,来查明这个跨站请求对于目的站点是不是安全可接受的。这样做,是因为跨站请求可能会对目的站点的数据造成破坏。 当请求具备以下条件,就会被当成预请求处理:

  • 请求以 GET, HEAD 或者 POST 以外的方法发起请求。或者,使用 POST,但请求数据为 application/
    x-www-form-urlencoded, multipart/form-data 或者 text/plain 以外的数据类型。比如说,用 POST 发送数据类型为 application/xml 或者 text/xml 的 XML 数据的请求。
  • 使用自定义请求头(比如添加诸如 X-PINGOTHER)

在这种情况时,后台的应用程序(比如express)必须处理OPTIONS请求或者添加一下代码

1
2
3
4
5
6
7
app.all('*', function(req, res, next) {
res.header("Access-Control-Allow-Origin", "*");// 允许跨域
res.header("Access-Control-Allow-Headers", "Content-Type,Content-Length, Authorization, Accept,X-Requested-With");//允许请求的数据类型
res.header("Access-Control-Allow-Methods","PUT,POST,GET,DELETE,OPTIONS");//允许的数据请求方法
res.header("Access-Control-Max-Age",3600 * 24);//预请求结果有效期
next();
});

附带证书请求

XMLHttpRequest和访问控制功能,最有趣的特性就是,发送凭证请求(HTTP Cookies和验证信息)的功能。一般而言,对于跨站请求,浏览器是不会发送凭证信息的。但如果将XMLHttpRequest的一个特殊标志位设置为true,浏览器就将允许该请求的发送。

跨域解决方案

  • 思路一:含有src或href属性的标签都是可以跨域的(eg:img、script、iframe、link等),使用这个思路可以有很多种跨域的方法
  • 思路二:使用CORS,W3C的一个标准,全称“跨域资源共享”(Crocs-origin resource sharing)

    通过jsonp跨域

  • 原理:script是可以跨域的,而且在跨域脚本中可以直接回调当前脚本的函数。
  • 限制:需要创建一个DOM对象并且添加到DOM树,只能用于GET方法

JSONP利用的是script可以跨域的特性,跨域URL返回的脚本不仅包含数据,还包含一个回调:

1
2
3
4
5
6
// URL: http://b.a.com/foo
var data = {
foo: 'bar',
bar: 'foo'
};
callback(data);

然后在我们在主站http://a.com中,可以这样来跨域获取http://b.a.com的数据:

1
2
3
4
5
6
// URL: http://a.com/foo
var callback = function(data){
// 处理跨域请求得到的数据
};
var script = $('<script>', {src: 'http://b.a.com/bar'});
$('body').append(script);

其实jQuery已经封装了JSONP得使用,我们可以这样来

1
2
3
$.getJSON( "http://b.a.com/bar?callback=callback", function( data ){
// 处理跨域请求得到的数据
});

通过iframe、img跨域

  • 原理:所有具有src属性的HTML标签都是可以跨域的,包括img, script
  • 限制:需要创建一个DOM对象,只能用于GET方法
1
2
3
4
5
var img = new Image();
img.src = 'http://some/picture'; // 发送HTTP请求
var ifr = $('<iframe>', {src: 'http://b.a.com/bar'});
$('body').append(ifr); // 发送HTTP请求

通过CORS

  • 原理:服务器设置Access-Control-Allow-OriginHTTP响应头之后,浏览器将会允许跨域请求
  • 限制:浏览器需要支持HTML5,可以支持POST,PUT等方法
1
2
Access-Control-Allow-Origin: * # 允许所有域名访问,或者
Access-Control-Allow-Origin: http://a.com # 只允许所有域名访问

window.postMessage

  • 原理:HTML5允许窗口之间发送消息
  • 限制:浏览器需要支持HTML5,获取窗口句柄后才能相互通信
1
2
3
4
5
6
7
8
// URL: http://a.com/foo
var win = window.open('http://b.com/bar');
win.postMessage('Hello, bar!', 'http://b.com');
// URL: http://b.com/bar
window.addEventListener('message',function(event) {
console.log(event.data);
});

在HTML5之前,JSONP已经成为跨域的事实标准了,jQuery都给出了支持。 值得注意的是它只是Hack,并没有产生额外的安全问题。 因为JSONP要成功获取数据,需要跨域资源所在服务器的配合,比如资源所在服务器需要自愿地回调一个合适的函数,所以服务器仍然有能力控制资源的跨域访问。

跨域的正道还是要使用HTML5提供的CORS头字段以及window.postMessage, 可以支持POST, PUT等HTTP方法,从机制上解决跨域问题。 值得注意的是Access-Control-Allow-Origin头字段是资源所在服务器设置的, 访问控制的责任仍然是在提供资源的服务器一方,这和JSONP是一样的。

文章目录
  1. 1. 什么是跨域
  2. 2. XMLHttpRequest 对象
  3. 3. 访问控制场景
    1. 3.1. 简单请求
    2. 3.2. 预请求
    3. 3.3. 附带证书请求
  4. 4. 跨域解决方案
    1. 4.1. 通过jsonp跨域
    2. 4.2. 通过iframe、img跨域
    3. 4.3. 通过CORS
    4. 4.4. window.postMessage
,