Obeta

你一定能看懂的浏览器跨域解读

关于浏览器跨域这个问题,前端开发者可能是最严重的'受害者'之一.而对于这个问题的讨论和疑惑将会一直持续下去.

前言

这里我不提供跨域的解决办法,毕竟 Google 能搜到几吨的答案.

所以这里只是简单(简单并不代表劣质,有时候代表了精髓)介绍跨域相关的信息,否则对不起我的标题了.

为什么会有跨域

简单的来说跨域是浏览器为了安全而限制的操作,开发 APP 的朋友可以离开了...

在互联网早期的时候,协议制定者们并没有考虑到不同源服务互相访问的安全问题,因此早期被一些黑客利用进而产生了一种著名的攻击方式:SCRF.

比如我是一名黑客,我有自己的一个网站(仿照淘宝),域名是111i.com,我想要被攻击人的1111.com网站上的数据,那么我会在自己的网站使用了Ajax请求1111.com网站,当被攻击人恰好在1111.com网站上登录过,那么我会得到被攻击人的信息数据.

后面把协议进行了更新,就直接默认禁止了不同源的请求发送,于是浏览器厂商就实行了这个提议,限制了两个不同域名之间的互相访问.当然并不是直接封死,也给了口子让你通过配置服务器的reponse header来设置.

因此除非服务器返回允许跨域请求头,否则浏览器默认不允许跨域访问.

以上所说的限制是:跨域请求已经发出,后端服务器已经响应,只是response header里并没有允许跨域的字段(下面会说),所以浏览器会拦截响应并报错.

如何避免

跨域是浏览器强制的,因此你无法改变它,我们能做的也就是避免这种情况.

只要你是在浏览器里面跑 ajax 请求,都无法避免这个跨域,除非服务器允许跨域,网上各种方法比如:jsonp,document.domain+iframe等等,这些都可以说是不"科学"的方法.

jsonp

比如用的挺多的jsonp,缺点非常明显,这里先介绍一下:

jsonp利用的是浏览器不对 HTML 中的script标签中的src字段进行跨域检测的"漏洞",这句话可能很长很绕开,我也懒得精简了,具体就是标签imglink还有script中的引用不会跨域,这就是jsonp的原理了.

jsonp在你请求ajax的时候生产一个script标签,标签里src的内容就是你的请求 url 和内容,然后传递一个 callback 参数给服务端,服务端返回数据时会将这个 callback 参数作为函数名来包裹住 JSON 数据,这样客户端就可以随意定制自己的函数来自动处理返回数据了.

还不明白?下面我们看栗子具体说说,也就是说你使用jsonp的时候会生成一个 script 标签:

<script
	type="text/javascript"
	src="url?参数名=参数值&callback=回调函数名"
></script>

服务器返回的脚本内容是:

//data是服务器产生的一个json数据
callback(data);

完整的栗子:

<script>
	function backData(data) {
		console.log(data);
	}
</script>
<script
	type="text/javascript"
	src="http://test.com?name=wowo&callback=backData"
></script>

<!-- 这个标签jsonp会动态添加的,callback参数说明是使用backData函数处理这段数据 -->

看完是不是觉得这个方法不错?其实不然,确定很明显而且很致命:

  1. 你也看到了参数都是在 src 属性上,因此只支持 get 请求.
  2. 需要服务器支持,服务器不支持的话 callback 参数将不起作用(致命缺点).
  3. JSONP 是一种非正式传输协议.

因此不管怎么样我们还是需要服务器的支持的.

header

jsonp是在客户端做的工作,毕竟没有修改服务器太多代码就能完成的,所以大多数人都采用这个办法,但是这种并不爽,而且还不能 post 数据(我要你何用?)

所以我们可以在服务器上动一点手脚,让服务器告诉浏览器允许这个域名ajax数据给我们的服务器.

我使用 Experss 做演示:

app.use((req, res, next) => {
	res.set({
		'Access-Control-Allow-Origin': 'obeta.me,www.zyx.com', //允许请求的域名
		'Access-Control-Allow-Headers':
			'Content-Type,Content-Length, Accept,X-Requested-With',
		'Access-Control-Allow-Methods': 'PUT,POST,GET,DELETE,OPTIONS',
	});
	if (req.method == 'OPTIONS') {
		res.send(200);
	}
	next();
});

上面我写了一个简单的中间件将所有的回应头部增加了跨域支持,告诉浏览器服务器同意obeta.me,www.zyx.com这两个域名跨域请求本站的所有资源,这样浏览器才允许你访问这个服务器的数据.

你可以查看回应头部,已经包括了如下的信息:

Access-Control-Allow-Headers → Content-Type,Content-Length, Authorization,
Accept,X-Requested-With Access-Control-Allow-Methods →
PUT,POST,GET,DELETE,OPTIONS Access-Control-Allow-Origin → * Connection →
keep-alive Content-Length → 12 Content-Type → application/json; charset=utf-8
Date → Fri, 25 Nov 2016 04:03:20 GMT ETag → W/"c-y5y6KX2lYb/xm63CpExrkw"
X-Powered-By → Express

Access-Control-Allow-Origin:允许的域,*表示允许所有的域名访问,但是这并不安全,所以在你的产品用户里必须去掉. Access-Control-Allow-Headers:允许浏览器发送的头部信息,如果你需要自定义头部信息发送数据,那么浏览器首先会使用 options 类型发送查询服务器是否支持这个头部,如果服务器支持那么浏览器才会让你自定义头部信息. Access-Control-Allow-Methods:允许的访问方法.

当然你可以设置其他的信息,这些都是针对浏览器做的事情.如果你是做 APP 开发的,你可以不用关心这些,毕竟你不用浏览器开发东西...

另外一点需要注意的是Access-Control-Allow-Methods,他的用处就是查询服务器对于当前路径(API)支持的方法.而OPTIONS请求,这是针对CORS-safelisted request header,也就是常说的简单请求,简单请求有以下几种:

  • Accept
  • Accept-Language
  • Content-Language
  • Content-Type

当你在请求头上设置除开这些这字段的时候浏览器首先会去询问OPTIONS服务器是否支持这个字符,如果支持就再进行真正的请求,如果不支持那就浏览器端报错...

总结

最后说清楚一点吧,有些人可能还觉得使用jsonp就可以跨域了,那是错误的,jsonp使用有局限性的,不能post数据,最重要的一点是需要服务器支持.网上太多的教程误导人,贴上代码就让别人以为可以用了,其实也是需要服务器去做适配支持的.

如果不想用jsonp,那么可以选择服务器设置一些允许你的域名就可以了,对于测试来说,当然设置Access-Control-Allow-Origin:"*"最好了.

个人随笔记录,内容不保证完全正确,若需要转载,请注明作者和出处.