啊。本来是打算昨天总结这块内容的。然而,突然忘了之前是在哪个地方看到的文章,找了半天硬是没找到,奈何就放弃了。好在今天乱刷着各种技术文章,终于看到了!!那么,请看跨域!
什么是跨域?
跨域是指一个域下的文档或者脚本试图去请求另一个域下的资源。这里描述的是广义的跨域。
- 资源跳转:A链接、重定向、表单提交
- 资源嵌入:
<link>、<script>/<img>/<frame>
等dom标签,以及样式中的background:url()
等的文件外链 - 脚本请求:js发起的ajax请求、dom和js对象的跨域操作。
然而,我们一般讨论的都是狭义的跨域,也就是由浏览器同源策略限制引起的一类请求场景。
什么是同源策略
同源策略/SOP(same origin policy)是一种约定,是浏览器最核心也是最基本的安全功能,如果缺乏了同源策略,浏览器很容易受到XSS、CSRF(跨站请求伪造)等算计。
场景:
假设用户在访问银行网站的时候并没有登出。他又跑到了任意其他网站上,刚好这个网站上有恶意的js代码,在后台请求银行网站的信息。因为用户目前仍然是银行站点的登录状态,那么恶意代码就可以在银行站点做任何事情。
所以同源策略限制以下几种行为:
- Cookie、LocalStorage 和 IndexDB无法读取
- DOM 和 JS对象无法获取
- AJAX请求不能发送
所谓的同源就是指“协议 + 域名 + 端口”三者要相同,即便两个不同的域名指向同一个ip地址,也非同源。具体的跨域场景看下表:
URL | 说明 | 是否允许通信 |
---|---|---|
http://www.domain.com/a.js http://www.domain.com/b.js http://www.domain.com/lab/c.js |
同一域名,不同文件或路径 | 允许 |
http://www.domain.com:8000/a.js http://www.domain.com/b.js |
同一域名,不同端口 | 不允许 |
http://www.domain.com/a.js https://www.domain.com/b.js |
同一域名,不同协议 | 不允许 |
http://www.domain.com/a.js http://x.domain.com/b.js http://domain.com |
主域相同,子域不同 | 不允许 |
http://www.domain.com/a.js http://www.domain2.com/b.js http://domain.com |
不同域名 | 不允许 |
跨域的解决方案
然而跨域的需求却是存在的,两个互相可信的站点之间可以保持信息沟通是存在需求的。于是,想办法实现跨域吧~~
jsonp实现跨域
有关jsonp的内容已经在之前的文章Ajax的实现(ajax与jsonp)/)说得挺详细的了,直接跳转吧~
但是jsonp有一个缺点:只能实现get一种请求。
document.domain + iframe跨域
此方案同样有所缺点:仅限主域相同,子域不同的跨域应用场景。
实现原理: 两个页面都通过js强制设置document.domain
为基础主域,就实现了同域。
父窗口:
|
|
子窗口
|
|
location.hash + iframe
实现原理:A域与B域要相互通信,通过中间页c来实现。三个页面,不同域之间利用iframe
的location.hash
传值,相同域之间直接用js访问来通信。
具体实现:A域:a.html => B域:b.html => A域:c.html, a 与 b 不同于只能通过hash值单向通信,b 与 c 也不同于也只能单向通信,但是c 与 a 同域,所以c可通过parent.parent访问a页面的所有对象。
a.html:(http://www.domain1.com/a.html)
|
|
b.html:(http://www.domain2.com/b.html)
|
|
c.html:(http://www.domain1.com/c.html)
|
|
window.name + iframe
window.name
的独特之处:name值在不同的页面(甚至不同的域名)加载后依旧存在,并且可以支持非常长的name值(2MB);
实现原理:通过动态创建一个iframe,访问跨域页,在跨域成功后跳转回同域的代理页,利用name长期存在的特性便可以获取到数据传递给同域的本页
a.html:(http://www.domain1.com/a.html)
|
|
proxy.html
中间代理页,与a.html同域,内容为空即可
b.html
|
|
总结: 通过iframe的src属性由外域转向本地域,跨域数据即由iframe的window.name从外域传递到本地域。这个就巧妙地绕过了浏览器的跨域访问限制,但同时它又是安全的操作。
postMessage跨域
postMessage是HTML5 XMLHttpRequest Level 2中的API,且是为数不多可以跨域操作的window属性之一,它可用于解决以下方面的问题:
- 页面和其打开的新窗口的数据传递(属于两个tab页之间的通信)
- 多窗口之间消息传递
- 页面与嵌套的iframe消息传递
- 上面三个场景的跨域数据传递
用法:postMesssage(data, origin)
方法接受两个参数
- data: html5规范支持任意基本类型或可复制的对象,但部分浏览器只支持字符串,所以传参时最好用JSON.stringify()序列化
- origin: 协议 + 主机 + 端口号,也可以设置为
*
,表示可以传递给任意窗口,如果要指定和当前窗口同源的话,设置为’/‘
a.html
|
|
b.html
|
|
跨域资源共享(CORS)
普通跨域请求: 只需要服务端设置Access-Control-Alloww-Origin
即可,前端无须设置。
带cookie请求: 前后端都需要设置字段,另外需注意:所带cookie为跨域请求接口所在域的cookie,而非当前页。
目前,所有浏览器都支持该功能(IE8+:IE8/9需要使用XDomainRequest对象来支持CORS)),CORS也已经成为主流的跨域解决方案。
CORS和JSONP对比
- JSONP只能实现GET请求,而CORS支持所有类型的HTTP请求。
- 使用CORS,开发者可以使用普通的XMLHttpRequest发起请求和获得数据,比起JSONP有更好的错误处理。
- JSONP主要被老的浏览器支持,它们往往不支持CORS,而绝大多数现代浏览器都已经支持了CORS)。
CORS与JSONP相比,无疑更为先进、方便和可靠。
有关CORS的详情,可以跳转详解CORS跨域资源共享
前端设置:
- 原生ajax12345678910111213var xhr = new XMLHttpRequest(); //IE8 9需要使用window.XDomainRequest兼容// 前端设置是否带cookiexhr.withCredentials = true;xhr.open('post', 'http://www.domain2.com:8080/login', ture);xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');xhr.send('user=admin');xhr.onreadystatechange = function() {if(xhr.readyState == 4 && xhr.status == 200) {alert(xhr.responseText);}}
服务端设置:
若后端设置成功,前端浏览器控制台则不会出现跨域报错信息,反之,说明没设成功。
nginx代理跨域(未写)
这里nginx的使用。。。没学过,所以只当挖了个坑吧 因为和下面的nodejs结合vue实现差不多,所以,以后有机会接触再回来补这里的
nodejs中间件代理跨域
- Vue框架的跨域(1次跨域)
利用node + webpack + webpack-dev-server代理接口跨域。在开发环境下,由于vue渲染服务和接口代理服务都是webpack-dev-server同一个,所以页面与代理接口之间不再跨域,无须设置headers跨域信息了。123456789101112131415module.exports = {entry: {},module: {},...devServer: {historyApiFallback: true,proxy: [{context: '/login',target: 'http://www.domain2.com:8080', // 代理跨域目标接口changeOrigin: true,cookieDomainRewrite: 'www.domain1.com' // 可以为false,表示不修改}],noInfo: true}}
在一次慕课网的学习,提到到dev-sever.js
进行手动设置代理
- 非vue(2次跨域)
利用node + express + http-proxy-middleware搭建一个proxy服务器。
前端代码
|
|
代理服务器
|
|
WebSocket协议跨域
详细可以看webSocket那一章吧~~
这里推荐使用Socket.io??