前言
我们在解决一个问题的时候应该先去了解这个问题是如何产生的,为什么会有跨域的存在呢?其实,最终的罪魁祸首都是浏览器的同源策略,浏览器的同源策略限制我们只能在相同的协议、IP地址、端口号相同,如果有任何一个不通,都不能相互的获取数据。并且,http和https之间也存在跨域,因为https一般采用的是443端口,http采用的是80端口或者其他。
同源策略是一种约定,它是浏览器最核心也最基本的安全功能,如果缺少了同源策略,浏览器很容易受到XSS、CSRF等攻击。所谓同源是指"协议+域名+端口"三者相同,即便两个不同的域名指向同一个ip地址,也非同源。
同源策略限制内容有:
1.Cookie、LocalStorage、IndexedDB 等存储性内容
2.DOM 节点
3.AJAX 请求发送后,结果被浏览器拦截了
但是有三个标签是允许跨域加载资源:
<img src=XXX>
<link href=XXX>
<script src=XXX>
常见的跨域场景
需要网络安全资料的朋友可以私信回复“资料”获取【点击查看】
当**协议、子域名、主域名、端口号中任意一个不相同时,**都算作不同域。不同域之间相互请求资源,就算作“跨域”。常见跨域场景如下图所示:
其实,跨域并不是请求发不出去,请求能发出去,服务端能收到请求并正常返回结果,只是结果被浏览器拦截了,常见的9种跨域方案,主要有: 通过jsonp跨域、 document.domain + iframe跨域、 location.hash + iframe、window.name + iframe跨域、postMessage跨域、跨域资源共享(CORS)、nginx代理跨域、nodejs中间件代理跨域、WebSocket协议跨域9种,并有着各自独特的跨域原理。
一:JSONP实现跨域
原理:jsonp实现跨域的原理是跨域的服务端把客户端所需要的数据放进客户端本地的一个js方法里,进行调用,客户端在本地的js对返回的数据进行处理。这样就实现了不同域名下的两个站点间的交流。
JSONP优点是简单兼容性好,可用于解决主流浏览器的跨域数据访问的问题。缺点是仅支持get方法具有局限性,不安全可能会遭受XSS攻击。
JSONP的实现流程:
- 声明一个回调函数,其函数名(如show)当做参数值,要传递给跨域请求数据的服务器,函数形参为要获取目标数据(服务器返回的data)。
- 创建一个{script}标签,把那个跨域的API数据接口地址,赋值给script的src,还要在这个地址中向服务器传递该函数名(可以通过问号传参:?callback=show)。
- 服务器接收到请求后,需要进行特殊的处理:把传递进来的函数名和它需要给你的数据拼接成一个字符串,例如:传递进去的函数名是show,它准备好的数据是show(‘我不爱你’)。
- 最后服务器把准备的数据通过HTTP协议返回给客户端,客户端再调用执行之前声明的回调函数(show),对返回的数据进行操作。
二:iframe+document.domain跨域
此方法的思想就是设置页面的document.domain把他们设置成相同的域名,比如都设置成xxx.com,这样来绕过同源策略。
代码里面的测试案列是,前端文件在7777端口,后台文件在8888端口,前端如果需要请求后端的数据就存在跨域,所以我们在后端8888端口写一个提供数据的中转html,然后通过ajax或者其他的方法请求到数据,然后把数据往外暴露,此方法需要2个html都需要设置相同的主域。
三:location.hash+iframe跨域
这种方法是一个很奇妙的方法,比如有一个这样的url:http://www.xxx.com#abc=123,那么我们通过执行location.hash就可以得到这样的一个字符串#abc=123,同时改变hash页面是不会刷新的。
假如现在我们有A页面在7777端口(前端显示的文件),B页面在8888端口,后台运行在8888端口。我们在A页面中通过iframe嵌套B页面。
从A页面要传数据到B页面
我们在A页面中通过,修改iframe的src的方法来修改hash的内容。然后在B页面中添加setInterval事件来监听我们的hash是否改变,如果改变那么就执行相应的操作。比如像后台服务器提交数据或者上传图片这些。
从B页面传递数据到A页面
经过上面的方法,那么肯定有聪明的朋友就在想那么,从B页面向A页面发送数据就是修改A页面的hash值了。对没错方法就是这样,但是我在执行的时候会出现一些问题。我们在B页面中直接:parent.location.hash = “#xxxx”
这样是不行的,因为前面提到过的同源策略不能直接修改父级的hash值,所以这里采用了一个我认为很巧妙的方法。部分代码:
如果可以直接修改我们就直接修改,如果不能直接修改,那么我们在B页面中再添加一个iframe然后指向C页面(我们暂时叫他代理页面,此页面和A页面是在相同的一个域下面),我们可以用同样的方法在url后面我们需要传递的信息。在代理页面中:parent.parent.location.hash = self.location.hash.substring(1);
只需要写这样的一段js代码就完成了修改A页面的hash值,同样在A页面中也添加一个setInterval事件来监听hash值的改变。
实现的核心思路就是通过修改URL的hash值,然后用定时器来监听值的改变来修改。所以说最大的问题就是,我们传递的数据会直接在URL里面显示出来,不是很安全,同时URL的长度是一定的所以传输的数据也是有限的。
四:window.name+iframe跨域
原理就是window.name属性在于加载不同的页面(包括域名不同的情况下),如果name值没有修改,那么它将不会变化,并且这个值可以非常的长(2MB)
方法原理:A页面通过iframe加载B页面。B页面获取完数据后,把数据赋值给window.name。然后在A页面中修改iframe使他指向本域的一个页面。这样在A页面中就可以直接通过iframe.contentWindow.name获取到B页面中获取到的数据。
五:postMessage跨域
postMessage是HTML5引入的API。他可以解决多个窗口之间的通信(包括域名的不同)。我个人认为他算是一种消息的推送,可以给每个窗口推送。然后在目标窗口添加message的监听事件。从而获取推送过来的数据。
六:跨域资源共享(CORS)
其实对于跨域资源的请求,浏览器已经把我们的请求发放给了服务器,浏览器也接受到了服务器的响应,只是浏览器一看我们2个的域不一样就把消息给拦截了,不给我们显示。所以我们如果我们在服务器就告诉浏览器我这个数据是每个源都可以获取就可以了。这就是CORS跨域资源共享。
这样的话,任何源都可以通过AJAX发起请求来获取我们提供的数据。针对不同语言的服务器后端有不一样的处理方法,但是实质是一样的。
七:NGINX代理跨域
· nginx配置解决iconfont跨域
浏览器跨域访问js、css、img等常规静态资源被同源策略许可,但iconfont字体文件(eot|otf|ttf|woff|svg)例外,此时可在nginx的静态资源服务器中加入以下配置。
location / {
add_header Access-Control-Allow-Origin *;
}
· nginx反向代理接口跨域
跨域原理:同源策略是浏览器的安全策略,不是HTTP协议的一部分。服务器端调用HTTP接口只是使用HTTP协议,不会执行JS脚本,不需要同源策略,也就不存在跨越问题。
实现思路:通过nginx配置一个代理服务器(域名与domain1相同,端口不同)做跳板机,反向代理访问domain2接口,并且可以顺便修改cookie中domain信息,方便当前域cookie写入,实现跨域登录。
nginx具体配置:
#proxy服务器server{
listen 81;
server_name www.domain1.com;
location/ {
proxy_pass http://www.domain2.com:8080; #反向代理
proxy_cookie_domain www.domain2.com www.domain1.com; #修改cookie里域名
index index.html index.htm;
# 当用webpack-dev-server等中间件代理接口访问nignx时,此时无浏览器参与,故没有同源限制,下面的跨域配置可不启用
add_header Access-Control-Allow-Origin http://www.domain1.com; #当前端只跨域不带cookie时,可为*
add_header Access-Control-Allow-Credentials true;
}
}
八:nodejs跨域
其实这种办法和上一种用nginx的方法是差不多的。都是你把请求发给一个中间人,由于中间人没有同源策略,他可以直接代理或者通过爬虫或者其他的手段得到想到的数据,然后返回(是不是和VPN的原理有点类似)。
当然现在常见的就是用nodejs作为数据的中间件,同样,不同的语言有不同的方法,但是本质是一样的。我上次自己同构自己的博客页面,用react服务器端渲染,因为浏览器的同源策略,请求不到数据,然后就用了nodejs作为中间件来代理请求数据。
九:webSocket跨域
webSocket大家应该都有所耳闻,主要是为了客服端和服务端进行全双工的通信。但是这种通信是可以进行跨端口的。所以说我们可以用这个漏洞来进行跨域数据的交互。
所以说,我们可以很轻松的构建基于webSocket构建一个客服端和服务端。代码在github建议大家都多多去运行一下,了解清楚。