跨域

同源策源

  同源政策的目的,是为了保证用户信息的安全,防止恶意的网站窃取数据。
  设想这样一种情况:A 网站是一家银行,用户登录以后,又去浏览其他网站。如果其他网站可以读取 A 网站的 Cookie,会发生什么?很显然,如果 Cookie 包含隐私(比如存款总额),这些信息就会泄漏。更可怕的是,Cookie 往往用来保存用户的登录状态,如果用户没有退出登录,其他网站就可以冒充用户,为所欲为。因为浏览器同时还规定,提交表单不受同源政策的限制。
  由此可见,“同源政策”是必需的,否则 Cookie 可以共享,互联网就毫无安全可言了。
  随着互联网的发展,“同源政策”越来越严格。目前,如果非同源,共有三种行为受到限制。
  1、Cookie、LocalStorage 和 IndexedDB 无法读取
  2、DOM 无法获得
  3、AJAX 请求无效(可以发送,但浏览器会拒绝接受响应)
  虽然这些限制是必要的,但是有时很不方便,合理的用途也受到影响。

产生跨域的原因

  浏览器限制、跨域、xhr请求,这三个条件同时发生才会产生跨域问题。

解决跨域问题

  CORS(Cross-Origin Resource Sharing)是目前主流的解决跨域的方案。除此之外,script、image、iframe的src都不受同源策略的影响,可以借助这一特点,实现跨域。

CORS

简单请求

  浏览器将CORS请求分成两类:简单请求(simple request)和非简单请求(not-so-simple request)。
  只要同时满足以下两大条件,就属于简单请求:
  1、请求方法是以下三种方法之一:HEAD、GET、POST。
  2、HTTP的头信息不超出以下几种字段:Accept、Accept-Language、Content-Language、Last-Event-ID、(Content-Type:只限于三个值application/x-www-form-urlencoded、multipart/form-data、text/plain)。
  凡是不同时满足上面两个条件,就属于非简单请求。
  CORS背后的基本思想,就是使用自定义的HTTP头部让浏览器与服务器进行沟通,从而决定请求或响应是应该成功,还是应该失败。
  对于简单请求,浏览器直接发出CORS请求。具体来说,就是在头信息之中,需要给它附加一个额外的origin头部,其中包含请求页面的源信息(协议、域名和端口),以便服务器根据这个头部信息来决定是否给予响应。
  浏览器如果发现跨源AJAX请求是简单请求,就自动在头信息之中,添加一个Origin字段。

Accept:*/*
Accept-Encoding:gzip, deflate, br
Accept-Language:zh-CN,zh;q=0.8,en;q=0.6
Connection:keep-alive
Content-Length:0
Host:www.webhuochai.com
Origin:http://127.0.0.1
Referer:http://127.0.0.1/cors.html
User-Agent:Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/57.0.2987.98 Safari/537.36

  上面的头信息中,Origin字段用来说明,本次请求来自哪个源(协议 + 域名 + 端口)。服务器根据这个值,决定是否同意这次请求。
  如果服务器认为这个请求可以接受,就在Access-Control-Allow-Origin头部中回发相同的源信息(如果是公共资源,可以回发" *" )。
  如果Origin指定的源,不在许可范围内,服务器会返回一个正常的HTTP回应。浏览器发现,这个回应的头信息没有包含Access-Control-Allow-Origin字段,就知道出错了,从而抛出一个错误,被XMLHttpRequest的onerror回调函数捕获。但是,这种错误无法通过状态码识别,因为HTTP回应的状态码有可能是200。
  [注意]请求和响应都不包含cookie信息

原生支持

  标准浏览器都通过XMLHttpRequest对象实现了对CORS的原生支持。在尝试打开不同来源的资源时,无需额外编写代码就可以触发这个行为。要请求位于另一个域中的资源,使用标准的XHR对象并在open()方法中传入绝对URL即可。

<input id="btn" type="button" value="跨域请求">
<div id="result"></div>
<script>
btn.onclick = function(){
    var xhr = new XMLHttpRequest();
    xhr.onreadystatechange = function(){
        if(xhr.readyState == 4){
            if ((xhr.status >= 200 && xhr.status < 300)|| xhr.status == 304){
                result.innerHTML = xhr.responseText;
            }else{
                alert("Request was unsuccessful: " + xhr.status);
            }    
        }
    };
    xhr.open("post", "https://www.baidu.com", true);
    xhr.send(null);    
}
</script>

  CORS主要需要在后端进行设置,以PHP为例。
  通过设置header()方法,“*”号表示允许任何域向服务端提交请求:

header( " Access-Control-Allow-Origin: * " );

  也可以设置指定的域名,如域名https://www.baidu.com,那么就允许来自这个域名的请求。

header( " Access-Control-Allow-Origin: https://www.baidu.com" );

  通过跨域XHR对象可以访问status和statusText属性,而且还支持同步请求。跨域XHR对象也有一些限制,但为了安全这些限制是必需的。
  1、不能使用setRequestHeader()设置自定义头部
  2、不能发送和接收cookie
  3、调用getAllResponseHeaders()方法总会返回空字符串

  由于无论同源请求还是跨源请求都使用相同的接口,因此对于本地资源,最好使用相对URL,在访问远程资源时再使用绝对URL。这样做能消除歧义,避免出现限制访问头部或本地cookie信息等问题。

Preflight

  CORS通过一种叫做Preflighted Requests(预检请求)的透明服务器验证机制支持开发人员使用自定义的头部、GET或POST之外的方法,以及不同类型的主体内容。
  在使用下列高级选项来发送请求时,就会向服务器发送一个Preflight请求。这种请求使用OPTIONS方法,发送下列头部
  1、Origin:与简单的请求相同
  2、Access-Control-Request-Method:请求自身使用的方法
  3、Access-Control-Request-Headers:(可选)自定义的头部信息,多个头部以逗号分隔。

Origin: http://www.nczonline.net
Access-Control-Request-Method: POST
Access-Control-Request-Headers: NCZ

  发送这个请求后,服务器可以决定是否允许这种类型的请求。服务器通过在响应中发送如下头部与浏览器进行沟通。
  1、Access-Control-Allow-Origin:与简单的请求相同
  2、Access-Control-Allow-Methods:允许的方法,多个方法以逗号分隔
  3、Access-Control-Allow-Headers:允许的头部,多个头部以逗号分隔
  4、Access-Control-Max-Age:应该将这个Preflight请求缓存多长时间(以秒表示)

Access-Control-Allow-Origin: http://www.nczonline.net
Access-Control-Allow-Methods: POST, GET
Access-Control-Allow-Headers: NCZ
Access-Control-Max-Age: 1728000
带凭据请求

  默认情况下,跨源请求不提供凭据(cookie、HTTP认证及客户端SSL证明等)。通过将withCredentials属性设置为true,可以指定某个请求应该发送凭据。
  如果服务器接受带凭据的请求,会用下面的HTTP头部来响应:

Access-Control-Allow-Credentials: true

  开发者必须在AJAX请求中打开withCredentials属性

var xhr = new XMLHttpRequest();
xhr.withCredentials = true;

  否则,即使服务器同意发送Cookie,浏览器也不会发送。或者,服务器要求设置Cookie,浏览器也不会处理。
  如果发送的是带凭据的请求,但服务器的响应中没有包含这个头部,那么浏览器就不会把响应交给JS(于是,responseText中将是空字符串,status的值为0,而且会调用onerror()事件处理程序)。
  需要注意的是,如果要发送Cookie,Access-Control-Allow-Origin就不能设为星号,必须指定明确的、与请求网页一致的域名。同时,Cookie依然遵循同源政策,只有用服务器域名设置的Cookie才会上传,其他域名的Cookie并不会上传,且(跨源)原网页代码中的document.cookie也无法读取服务器域名下的Cookie。

跨浏览器
function createCORSRequest(method, url){
    var xhr = new XMLHttpRequest();
    //标准浏览器
    if("withCredentials" in xhr){
        xhr.open(method, url, true);
    //IE10-浏览器
    }else if(typeof XDomainRequest != "undefined"){
        xhr = new XDomainRequest();
        xhr.open(method, url); 
    } 
    return xhr;
}
ajax跨域的表现

  第一种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 404
  出现这种情况的原因如下:
  本次ajax请求是“非简单请求”,所以请求前会发送一次预检请求(OPTIONS)
服务器端后台接口没有允许OPTIONS请求,导致无法找到对应接口地址。
  解决方案: 后端允许options请求

  第二种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且The response had HTTP status code 405
这种现象和第一种有区别,这种情况下,后台方法允许OPTIONS请求,但是一些配置文件中(如安全配置),阻止了OPTIONS请求,才会导致这个现象。
  解决方案: 后端关闭对应的安全配置

  第三种现象:No 'Access-Control-Allow-Origin' header is present on the requested resource,并且status 200。
  这种现象和第一种和第二种有区别,这种情况下,服务器端后台允许OPTIONS请求,并且接口也允许OPTIONS请求,但是头部匹配时出现不匹配现象。
  比如origin头部检查不匹配,比如少了一些头部的支持(如常见的X-Requested-With头部),然后服务端就会将response返回给前端,前端检测到这个后就触发XHR.onerror,导致前端控制台报错。
  解决方案: 后端增加对应的头部支持。

  第四种现象:heade contains multiple values '*,*'
表现现象是,后台响应的http头部信息有两个Access-Control-Allow-Origin:*
  这种问题出现的主要原因就是进行跨域配置的人不了解原理,导致了重复配置。

图片Ping

  在CORS出现以前,要实现跨域Ajax通信颇费一些周折。开发人员想出了一些办法,利用DOM中能够执行跨域请求的功能,在不依赖XHR对象的情况下也能发送某种请求。
  图像Ping跨域请求技术是使用<img>标签。一个网页可以从任何网页中加载图像,不用担心跨域不跨域。这也是在线广告跟踪浏览量的主要方式。也可以动态地创建图像,使用它们的onload和onerror事件处理程序来确定是否接收到了响应。
  动态创建图像经常用于图像Ping:图像Ping是与服务器进行简单、单向的跨域通信的一种方式。 请求的数据是通过査询字符串形式发送的,而响应可以是任意内容,但通常是像素图或204响应。通过图像Ping,浏览器得不到任何具体的数据,但通过侦听load和error事件,它能知道响应是什么时候接收到的。
  图像Ping最常用于跟踪用户点击页面或动态广告曝光次数。图像Ping有两个主要的缺点,一是只能发送GET请求,二是无法访问服务器的响应文本。因此,图像Ping只能用于浏览器与服务器间的单向通信。

<input id="btn" type="button" value="跨域请求">
<div id="result"></div>
<script>
var add = (function(){
    var counter = 0;
    return function(){
        return ++counter;
    }
})();
btn.onclick = function(){
    var sum = add();
    var img = result.getElementsByTagName('img')[0];
    if(!img){
        var img = new Image();        
    }
    img.height="100";
    img.onload = img.onerror = function(){
        result.appendChild(img);
        var oSpan = document.getElementById('sum');
        if(!oSpan){
            oSpan = document.createElement('span');
            oSpan.id="sum";
        }
        oSpan.innerHTML = '发送请求的次数:' + sum;
        result.appendChild(oSpan);
    };    
    if(sum%2){
        img.src = "http://7xpdkf.com1.z0.glb.clouddn.com/eg_bulboff.gif?sum="+sum;    
    }else{
        img.src = "http://7xpdkf.com1.z0.glb.clouddn.com/eg_bulbon.gif?sum="+sum;    
    }
}
</script>

JSONP

  JSONP之所以在开发人员中极为流行,主要原因是它非常简单易用,老式浏览器全部支持,服务器改造非常小。与图像Ping相比,它的优点在于能够直接访问响应文本,支持在浏览器与服务器之间双向通信。
  不过,JSONP也有两点不足:首先,JSONP是从其他域中加载代码执行。如果其他域不安全,很可能会在响应中夹带一些恶意代码,而此时除了完全放弃JSONP调用之外,没有办法追究。因此在使用不是自己运维的Web服务时,一定得保证它安全可靠;其次,要确定JSONP请求是否失败并不容易。虽然HTML5给<script>元素新增了一个onerror事件处理程序,但目前还没有得到任何浏览器支持。为此,开发人员不得不使用计时器检测指定时间内是否接收到了响应。但就算这样也不能尽如人意,毕竟不是每个用户上网的速度和带宽都一样。
  百度搜索框就是使用了JSONP的技术,在百度搜索的URL中,有用的查询如下

https://sp0.baidu.com/5a1Fazu8AA54nxGko9WTAnF6hhy/su?wd=123&&cb=a

结果为:

a({q:"123",p:false,s:["12306","12306铁路客户服务中心","12308汽车订票官网","12306火车票网上订票官网","12333","12315","12345","12333社保查询网","123网址之家","12366"]});

iframe

  jsonp是使用script标签,imgPing是使用image标签,接下里使用iframe标签实现跨域。
  iframe元素可以在当前网页之中,嵌入其他网页。每个iframe元素形成自己的窗口,即有自己的window对象。iframe窗口之中的脚本,可以获得父窗口和子窗口。但是,只有在同源的情况下,父窗口和子窗口才能通信;如果跨域,就无法拿到对方的DOM
  如果两个窗口一级域名相同,只是二级域名不同,可以通过设置document.domain来使其通信。
  锚点值,又称为片段标识符(fragment identifier),指的是URL的#号后面的部分,比如http://example.com/x.html#fragment的#fragment。如果只是改变片段标识符,页面不会重新刷新。父窗口可以把信息,写入子窗口的锚点值。
  HTML5为了解决这个问题,引入了一个全新的API:跨文档通信 APIpostMessage()

websocket

  websocket是HTML5开始提供的一种浏览器与服务器进行全双工通讯的网络技术,属于应用层协议,它基于TCP传输协议,并复用HTTP的握手通道,是用以在网页浏览器和服务器建立一个 socket 连接的API。通俗地讲:在客户端和服务器保有一个持久的连接,两边可以在任意时间开始发送数据。

websocket的优势

支持双向通信,实时性更强。
更好的二进制支持。
较少的控制开销。连接创建后,ws客户端、服务端进行数据交换时,协议控制的数据包头部较小。

socket.io

  socket.io是一个websocket库,包括了客户端的js和服务器端的nodejs,它的目标是构建可以在不同浏览器和移动设备上使用的实时应用。

socket.io的特点

易用性:socket.io封装了服务端和客户端,使用起来非常简单方便。
跨平台:socket.io支持跨平台,这就意味着你有了更多的选择,可以在自己喜欢的平台下开发实时应用。
自适应:它会自动根据浏览器从WebSocket、AJAX长轮询、Iframe流等等各种方式中选择最佳的方式来实现网络实时应用,非常方便和人性化,而且支持的浏览器最低达IE5.5。

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

推荐阅读更多精彩内容