前端跨域总结
本文由 小茗同学 发表于 2018-05-14 浏览(2679)
最后修改 2018-05-14 标签:跨域

跨域最常见的还是CORS和服务端代理,其它的虽然有些不太常用,但还是总结一下。需要说明一下的时,浏览器的跨域限制包括:

  • 前端与服务器的跨域限制;
  • 前端窗口之间通信的跨域限制;

本文主要讲前者,后者有单独一篇文章介绍。

图片法

这种方法利用了图片没有跨域限制的特点,仅适用于不需要获取服务端响应的场景,如日志统计等,且只能以GET方式请求,一般很少用。

var img = new Image();
img.src = 'http://other.domain.com/log.png';

JSONP

XmlHttpRequest有跨域限制,但是script标签没有,利用这个特点,可以跨域访问到某个接口返回的内容,但由于script标签只能返回js,所以一般将JSON内容嵌入返回的js中,这种方法就叫JSONPJSON with Padding),同时为了获取回调,一般在请求的时候会把回调函数以某种方式告诉后台让其在返回的时候主动触发。

JSONP一般是这样工作的:

// 简单的JSONP前端实现
function jsonp(url, success) {
	var callbackId = '_jsonp_' + Date.now();
	window[callbackId] = success;
	url += (url.indexOf('?') >= 0 ? '&' : '?') + 'callback='+callbackId;
	var element = document.createElement('script');
	element.setAttribute('type', 'text/javascript');
	element.setAttribute('src', url);
	document.body.appendChild(element);
}

Java后台实现时:

// 假设这是原本需要返回的JSON内容
String callbackId = request.getParameter("callback");
String data = "{\"code\": 0, \"text\": \"操作成功!\"}";
data = ";(function(){var data = "+data+"; if(window['"+callbackId+"']) window['"+callbackId+"']();})();"
PrintWriter out = response.getWriter();
response.setContentType("application/javascript;charset=utf-8");
out.write(data);
out.flush();
out.close();

JSONP麻烦不说,而且需要前后端一起配合,再加上后面将要提到的CORS兼容性不错,所以JSONP现在几乎很少见了。

CORS

CORS全称Cross Origin Resource Sharing,意为跨域资源共享。详解之前写的单独一篇博文:http://blog.haoji.me/cors.html

window.name + iframe

准确来说window.name解决的是前端窗口之间的跨域通信,但是通过iframe的配合可以实现与服务器的跨域通信。

原理:在浏览器中打开一个页面后,无论页面地址如何变化,它的window.name始终都不会变。同时,此特性同样适用于iframe,无论iframesrc如何变化,它的window.name都不会变。而且window.name是可写的,最大可以写入2M,所以利用window.name可以做很多事。

测试:你可以这样测试一下,先打开一个百度页面,然后打开控制台,给window.name随便赋一个值,然后点击链接跳转其它页面,再次打印window.name看有没有变化。

假设如下页面地址为www.aaa.com/index.html

<!DOCTYPE html>
<html>
<head>
	<title>window.name跨域测试</title>
	<meta charset="UTF-8">
</head>
<body>
	<a href="javascript:crossDomainByWindowName()">跨域请求</a>
	<script>
	function crossDomainByWindowName() {
		var iframe = document.createElement('iframe');
		iframe.src = 'http://www.bbb.com/proxy.html';
		iframe.style.display = 'none'; // 隐藏
		document.body.appendChild(iframe);
		var count = 0;
		iframe.onload = function() {
			if(count == 0) {
				// 指向同域的某个没用的空白页面
				iframe.src = 'http://www.aaa.com/empty.html';
			} else {
				console.log(iframe.contentWindow.name);
				alert('跨域拿到数据:' + iframe.contentWindow.name)
				document.body.removeChild(iframe);
			}
			count++;
		};
	}
	</script>
</body>
</html>

以下是需要跨域的网站www.bbb.com/proxy.html,可以把它写成一个通用的代理页面:

<!DOCTYPE html>
<html lang="zh">
<head>
	<meta charset="utf-8">
	<meta name="viewport" content="width=device-width, initial-scale=1.0">
	<title>window.name跨域代理</title>
</head>
<body>
	<script type="text/javascript">
		// 假设这是通过调用某个接口返回的数据
		var data = {code: 0, text: '处理成功!'};
		window.name = JSON.stringify(data);
	</script>
</body>
</html>

完整演示见:http://demo.haoji.me/2018/05/14-cross-domain/

window.postMessage

严格来讲,window.postMessage实现的其实是前端窗口跨域通信,不属于本文范畴,但是窗口之间跨域都解决了,前端与服务器之间的跨域肯定也没问题了,比如说,我们也可以利用window.postMessage+iframe来实现前后端跨域,具体和window.name类似,这里就不重复讲了。

代理

这是比较常见的做法,跨域限制针对的仅仅是浏览器,后台访问某个http接口时不存在跨域问题,所以,碰到跨域问题时一般都喜欢交由后台代理访问,拿到数据之后再返回前端,最常见的应该是nginx代理了:

server {
	listen	   80;
	server_name  www.aaa.com;
	location /cgi-bin/ {
		proxy_set_header X-Real-IP $remote_addr;
		proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
		proxy_set_header Host $http_host;
		proxy_pass http://www.bbb.com;
	}
}

总结

有些方法是历史遗留,现在已经很少使用了,最常用的还是CORS和nginx反向代理。