跨域,同源策略以及解决方案

跨域

跨域是指从一个域名的网页去请求另一个域名的资源。比如从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 字段,浏览器抛出一个错误,被 XMLHttpRequestonerror 回调函数捕获。


对于非简单请求,浏览器检测到跨源AJAX请求是非简单请求,在正式通信之前,会增加一次预检请求,作用是询问服务器当前网页所在的域名是否在服务器的许可名单之中,以及可以使用哪些HTTP动词和头信息字段;只有得到肯定答复,浏览器才会发出正式的跨域请求。"预检请求“的使用,可以避免跨域请求对服务器的用户数据产生未预期的影响

预检请求的请求方法是options,在options请求头中会添加Origin字段,还有Access-Control-Request-Method、Access-Control-Request-Headers。服务器收到预检请求后,根据OriginAccess-Control-Request-MethodAccess-Control-Request-Headers确认是否允许跨源请求。如果同意这次请求,会在回应的头信息中添加Access-Control-Allow-OriginAccess-Control-Allow-MethodsAccess-Control-Allow-Headers。如果不同意这次请求,服务器也会返回正常的HTTP回应,但是没有任何CORS相关字段,浏览器抛出一个错误,被XMLHttpRequestonerror回调函数捕获。服务器通过预检请求后,浏览器将发送正常的cors请求。

由此可见,当触发预检时,一次AJAX请求会消耗掉两个TTL,严重影响性能。那么如何节省掉OPTIONS请求来提升性能呢?从上文可以看出,有两个方案:
1,发出简单请求
2,服务器端设置Access-Control-Max-Age字段:当第一次请求该URL时会发出OPTIONS请求,浏览器会根据返回的Access-Control-Max-Age(表示可以缓存Access-Control-Allow-Methods和Access-Control-Allow-Headers提供的信息多长时间,单位秒,由服务端和浏览器默认值共同决定)字段缓存该OPTIONS预检请求的响应结果。在缓存有效期内,该资源的请求(URL和header字段都相同的情况下)不会再触发预检。(chrome 打开控制台可以看到,当服务器响应Access-Control-Max-Age时只有第一次请求会有预检,后面不会了。注意要开启缓存,去掉disable cache勾选)
但是要注意的是,该缓存只针对这一个请求 URL 和相同的 header,无法针对整个域或者模糊匹配 URL 做缓存(当然也可以考虑封装一下,固定一个接口地址,传不同的body内容)。

标准的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。推荐服务端代理,兼容性好,强大。要考虑流量问题,考虑服务端配置的维护问题。

最后编辑于
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 213,014评论 6 492
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 90,796评论 3 386
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 158,484评论 0 348
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 56,830评论 1 285
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 65,946评论 6 386
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,114评论 1 292
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,182评论 3 412
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 37,927评论 0 268
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 44,369评论 1 303
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 36,678评论 2 327
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 38,832评论 1 341
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 34,533评论 4 335
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,166评论 3 317
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 30,885评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,128评论 1 267
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 46,659评论 2 362
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 43,738评论 2 351

推荐阅读更多精彩内容

  • 题目1.什么是同源策略? 同源策略(Same origin Policy): 浏览器出于安全方面的考虑,只允许与本...
    FLYSASA阅读 1,715评论 0 6
  • 1. 什么是跨域 跨域,是指浏览器不能执行其他网站的脚本。它是由浏览器的同源策略造成的,是浏览器对JavaScri...
    cbw100阅读 6,315评论 2 86
  • 前言:对于跨域请求,很早之前就有去了解过,但因为一直关注的都是服务器后端开发,故也就仅仅停留在概念的理解上而没有机...
    ken_ljq阅读 89,743评论 6 128
  • 欢迎关注微信公众号:全栈工厂 本文主要参考跨域资源共享 CORS 详解[http://www.ruanyifeng...
    liqingbiubiu阅读 1,830评论 0 3
  • 一个人所追求的生活方式取决于他活过的生命中阅读过的书籍、听过的音乐、接受的教育以及领略过的风景、人情,所有这一切所...
    望舒ws阅读 189评论 1 0