跨域
跨域是指从一个域名的网页去请求另一个域名的资源。比如从www.baidu.com页面去请求www.google.com的资源,一般情况下不能这么做,它是由浏览器的同源策略造成的,跨域的严格一点的定义是:只要协议,域名,端口有任何一个的不同,就被当作是跨域。
同源策略
若地址里面的协议、域名和端口号均相同则属于同源。这个策略可以阻止一个页面上的恶意脚本通过页面的DOM对象获得访问另一个页面上敏感信息的权限。
同源策略有哪些限制:
(1)Cookie、LocalStorage、IndexDB无法读取
(2)DOM无法获得
(3)AJAX请求不能发送
浏览器的同源策略会导致跨域,这里同源策略又分为以下两种:
(1)DOM同源策略:禁止对不同源页面DOM进行操作。这里主要场景是iframe跨域的情况,不同域名的iframe是限制互相访问的。
(2)XmlHttpRequest同源策略:禁止使用XHR对象向不同源的服务器地址发起http请求。
可能嵌入跨源资源的情况
(1)<script src="..."></script>
(2)<link rel=“stylesheet” href=“...”>,需要一个设置正确的Content-Type消息头
(3)<img>
(4)<video>和<audio>
(5)<object>,<embed>和<applet>的插件
(6)@font-face引入的字体,不同浏览器限制不同
(7)<frame>和<iframe>载入的任何资源,站点可以使用X-Frame-Options消息头来阻止这种形式的跨域交互
解决方案
(1)跨域资源共享(CORS)
CORS是W3C标准,是跨源AJAX请求的根本解决方法。基本思想是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是成功还是失败。
CORS将请求分为简单请求和非简单请求。只要同时满足以下两大条件就是简单请求,否则是非简单请求
(1) 请求方法是以下三种方法之一: HEAD,GET,POST
(2)HTTP的头信息:没有设置自定义的HTTP头,Content-Type(表示具体请求中的媒体类型信息):只限于三个值 application/x-www-form-urlencoded
(表单默认的提交数据的格式)、multipart/form-data
( 需要在表单中进行文件上传时,就需要使用该格式)、text/plain
(纯文本格式)
对于简单请求,浏览器检测到跨源AJAX请求是简单请求,自动在头信息之中添加 origin 字段(源:协议+域名+端口),服务器根据这个值,决定是否同意这次请求。如果同意这次请求,会在回应的头信息中添加 Access-Control-Allow-Origin
字段。如果不同意这次请求,服务器也会返回正常的HTTP回应,但是不包含Access-Control-Allow-Origin 字段,浏览器抛出一个错误,被 XMLHttpRequest
的onerror
回调函数捕获。
对于非简单请求,浏览器检测到跨源AJAX请求是非简单请求,在正式通信之前,会增加一次预检请求。预检请求的请求方法是options,在options请求头中会添加Origin字段,还有Access-Control-Request-Method、Access-Control-Request-Headers。服务器收到预检请求后,根据
Origin
、Access-Control-Request-Method
和Access-Control-Request-Headers
确认是否允许跨源请求。如果同意这次请求,会在回应的头信息中添加Access-Control-Allow-Origin
、Access-Control-Allow-Methods
和Access-Control-Allow-Headers
。如果不同意这次请求,服务器也会返回正常的HTTP回应,但是没有任何CORS相关字段,浏览器抛出一个错误,被XMLHttpRequest
的onerror
回调函数捕获。服务器通过预检请求后,浏览器将发送正常的cors请求。标准的CORS请求不对cookies做任何事情,既不发送也不改变。如果希望改变这一情况,就需要将withCredentials设置为true。ajax请求中加上字段 xhrFields: { withCredentials: true },这样可以携带上cookie
这样后台配置就出现了限制,需要配置一个解决跨域访问的过滤器,服务端在处理这一请求时,也需要将Access-Control-Allow-Credentials
设置为true,而且header字段Access-Control-Allow-Origin的值不能为"*", 必须是一个确定的域
(2)JSONP
JSONP的原理:在js中,我们直接用XMLHttpRequest请求不同域上的数据时,是不可以的。但是,在页面上引入不同域上的js脚本文件却是可以的,主要是利用了<script src="..."></script>可以嵌入跨源资源,请求一段javascript代码,然后执行 JavaScript 代码来实现跨域。前端实现代码如下:
服务器根据callback函数名返回的js文件中的代码如下:
首先在客户端注册一个callback, 然后把callback的名字传给服务器。此时,服务器先生成 json 数据。 然后以 javascript 语法的方式,生成一个function , function 名字就是传递上来的参数。最后将 json 数据直接以入参的方式,放置到 function 中,这样就生成了一段 js 语法的文档,返回给客户端。客户端浏览器,解析script标签,并执行返回的 javascript 文档,此时数据作为参数,传入到了客户端预先定义好的 callback 函数里。(动态执行回调函数)用JSONP抓到的数据并不是JSON,而是任意的JavaScript,用 JavaScript解释器运行而不是用JSON解析器解析
JSONP由回调函数和数据组成。回调函数是当响应到来时应该在页面中调用的函数,而数据就是传入回调函数中的JSON数据。注意:JSONP只支持GET请求,不支持POST请求。
如果使用jquery,那么通过它封装的方法就能很方便的来进行jsonp操作了。
<script type="text/javascript">
$.getJSON('http://example.com/data.php?callback=?,function(jsondata)'){
//处理获得的json数据
});
</script>
jquery会自动生成一个全局函数来替换callback=?中的问号,之后获取到数据后又会自动销毁,实际上就是起一个临时代理函数的作用。$.getJSON方法会自动判断是否跨域,不跨域的话,就调用普通的ajax方法;跨域的话,则会以异步加载js文件的形式来调用jsonp的回调函数。
优点:它的兼容性更好,在更加古老的浏览器中都可以运行,不需要XMLHttpRequest或ActiveX的支持;并且在请求完毕后可以通过调用callback的方式回传结果,能够直接访问响应文本,可用于浏览器与服务器间的双向通信。
缺点:它只支持GET请求而不支持POST等其它类型的HTTP请求;它只支持跨域HTTP请求这种情况,不能解决不同域的两个页面之间如何进行JavaScript调用的问题;JSONP从其他域中加载代码执行,其他域可能不安全;难以确定JSONP请求是否失败。
(3)ServerProxy 服务器代理
当没有提供JSONP等其他访问权限、基于安全考虑或者不只是发送get请求的时候,可以考虑ServerProxy。这是目前比较常用的跨域方法。
原理:新建一个站点(代理),将ajax请求发送到代理路径下,代理发送http请求到服务器。把跨域的请求放到了nginx层(服务器端),就没有同源策略的限制了。
(4)document.domain
适用情况:一二级域名相同,三级域名不同,举例:mes.baidu.com 与 class.baidu.com
原理:
设置document.domain=“[baidu.com](http://baidu.com)”
服务器设置Cookie时,指定Cookie的所属域名为二级域名
消除限制:Cookie、DOM
(5)跨文档通信
这种方式允许一个页面的脚本发送文本信息到另一个页面的脚本中,不管脚本是否跨域。在一个window对象上调用postMessage()会异步的触发window上的onmessage事件,然后触发定义好的事件处理方法。一个页面上的脚本仍然不能直接访问另外一个页面上的方法或者变量,但是他们可以安全的通过消息传递技术交流。
(6)postMessage
适用情况:不论两个窗口是否同源,通过发送消息的方式进行通信。子窗口是通过iframe或者window.open的方式打开的窗口。
原理:利用 postMessage(messageContent,receiveOrigin) 发送消息,窗口监听 message 事件,获取相关信息。
Message 事件的事件对象 event,提供3个属性:
event.source:发送消息的源
event.origin:消息发向的网址
event.data:消息内容。
发送消息的网页添加postMessage
接收消息的网页添加监听事件
(7)hash
如果只是改变hash值,页面不会重新刷新。
原理:A窗口中的iframe嵌入了B窗口,A窗口可以把想传递的信息放在iframe中的src的链接中,B窗口可以通过监听 hashChange 事件得到通知。B窗口也可以改变A窗口的hash值,A窗口通过监听 hashChange 事件得到通知。
父窗口设置子窗口的hash值
子窗口监听hash事件
子窗口设置父窗口的hash值
父窗口监听hash事件
hash是个什么东西?
最早的时候,它用来做锚点。比如说页面里面有10段文字,每段文字有一个加个锚点,可以通过在url尾部添加hash的方式定位到其中一段(浏览器滚动到对应位置)。hash的好处在于它改变时不给服务端发请求,后来就有一些鸡贼的人使用这个来实现SPA(单页应用),来区分当前应用显示的是哪个页面。
总结:当前主流的解决方案,或者说应该用哪种方案?
我们目前主要是ajax的形式跨域,那就只能在JSONP, 服务端代理,和CORS三种方法中选。看业务要求,如果完全没有post请求,可选JSONP。如果不需要支持IE9及之前的旧版本浏览器,可选CORS。推荐服务端代理,兼容性好,强大。要考虑流量问题,考虑服务端配置的维护问题。
网友评论