欢迎访问 licqi IT技术
我们一直在努力

CORS机制及其风险

licqi阅读(710)

根据同源策略,浏览器默认是不允许XMLHttpRequest对象问非同一站点下的资源的,即用ajax方式访问非同一域名下的资源会出错。比如当google要通过ajax去访问百度的数据,是不行的。

所谓同源,是要求协议,域名,端口都相同。

比如 http://www.aaa.com 和下列URL相比,都不属于同源。

https://www.aaa.com

http://www.aaa.com:8080

http://aaa.com

但下面这种属于同源:

http://username:password@www.aaa.com

禁用跨域访问资源是为了安全,但会牺牲便利性。因此就出现了好几种跨域访问资源的方法。其中一种是称之为CORS的技术(Cross-origin resource sharing)。

CORS的概念

所谓CORS(Cross-origin resource sharing ),是指通过XMLHttpRequest的ajax方式访问其他域名下资源,而不是在A域名的页面上点击打开一个B域名的页面。引入不同域上的js脚本文件也是没用问题的。出于安全考虑,跨域请求不能访问document.cookie对象。

CORS技术允许跨域访问多种资源,比如javascript,字体文件等,这种技术对XMLHttpRequest做了升级,使之可以进行跨域访问。但不是所有的浏览器都支持CORS技术。Firefox和Chrome等浏览器支持的比较好,稍微新一点的版本都支持。IE比较搓,IE10才真正支持这个机制,IE10以下需要用XDomainRequest这个对象,这是IE特有的。

当然不是说浏览器支持了就立刻可以跨域访问了,CORS技术中最重要的关键点是响应头里的Access-Control-Allow-Origin这个Header。 此Header是W3C标准定义的用来检查是可否接受跨域请求的一个标识。实现过程大致如下:A域名中发起跨域请求到B域名,B域名如果发送响应头Access-Control-Allow-Origin: A域名,那么A域名的这次跨域请求就能成功。反之则不成功。这里有一个称之为Preflighted requests 的步骤,这一步骤中,浏览器发起一个OPTIONS请求,去服务器验证是否支持跨域访问。(

用chrome去查看这个option请求始终看不到,只有当跨域访问请求非正常(比如本人笔误,将请求type的get误写成了gey)的情况下,才会看到有个OPTIONS请求,如图:

非常奇怪,不知道为何。

CORS的实现

举个例子。

有2个站点,分别绑定了www.myhost1.com和www.myhost2.com:8001这个两个域名。

假设www.myhost2.com:8001下的a.html文件,需要访问www.myhost1.com域名下的b.aspx文件,代码如下。

a文件代码如下,点击按钮就会去跨域访问b页面:

  
    
    
    
Apage    
    
    
$(function () {    
var options = {    
type: 'get',    
url: "
http://www.myhost1.com/mysite/cors/b.aspx
",    
success: function (result) {    
alert(result);    
}    
};    
$("#btn1").click(function () {    
$.ajax(options);    
});    
});    
    
    
    
    
    

b文件为一个aspx文件,代码非常简单,显示时间


A文件的地址为http://www.myhost2.com:8001/a.html

A文件的请求是按照get方式读取的,当A文件点击按钮,会弹出B文件的内容。由于同源策略,会看到如下错误:

为了避免这个错误,在www.myhost1.com主机的Http响应标头里加上Access-Control-Allow-Origin:http://www.myhost2.com:8001,注意不要打上最后一个斜杠/,意思是允许http://www.myhost2.com:8001这个域名中的XMLHttpRequest对象跨域访问www.myhost1.com主机下的资源。

还有一种方法是修改web.config,Access-Control-Allow-Origin的值可以是通配符*,比如如下:



    
     
       
     
   
  

或者通过代码添加响应标头来实现。

     

当浏览器通过ajax请求访问其他域名下的资源时,如果那个资源的响应头中包含了Access-Control-Allow-Origin,则可以请求成功,否则就会失败,是否设置了响应头,可以在firebug等调试工具内看到。

无响应头

有响应头

跨域访问的风险

默认情况下,CORS机制不读取cookie值,即跨域访问时没有带上cookie,当需要跨域访问带cookie时,需要设置另一个文件头,Access-Control-Allow-Credentials,值为true。该选项设定了是否允许跨域请求带cookie。当设定为true后,对于XMLHttpRequest还需要设定withCredentials为true,在Jquery中为xhrFields: {withCredentials: true}

一旦允许了带cookie的跨域访问,那么遭到CSRF***的概率大大的增加了,比如之前的假设a.html页面内有一段恶意的js,它每隔1分钟去跨域请求用户b页面,并将请求后的数据偷偷的存入自己的数据库内。假设b页面需在要用户登录的情况下,可以请求到很多机密数据,比如信用卡号等,那当用户打开a页面后,在非授意的情况下,获取到b面值的内容,引发泄密。

对之前的演示代码做小的修改:

a页面代码:




Apage


$(function 
() {
var options = {
type: 'get',
xhrFields: {withCredentials: true},
url: "http://www.myhost1.com/mysite/cors/b.aspx",
success: 
function (result) {
alert(result);
}
};
$("#btn1").click(function () 
{
$.ajax(options);
});
});





b.aspx页面代码如下:

  
  

假设b页面的用户登录了一下,种植了一个session,那么就会有一个sessionid被存储到用户的cookie中去,此时,a页面点击按钮后,会获取b页面显示的内容,根据***的行为,可以有更大的破坏力。

演示图:当b页面未登录时,显示未登录。请求中不带cookie。

但是当b页面已经登录,则在a页面中的跨域请求中,带有cookie,会显示已登录的页面。

参考资料:

http://drops.wooyun.org/tips/188

http://en.wikipedia.org/wiki/Cross-origin_resource_sharing

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS

https://dev.opera.com/articles/dom-access-control-using-cors/

http://blog.darkthread.net/post-2014-09-29-cors-options-preflight-and-iis.aspx

http://www.cnblogs.com/idche/p/3190926.html

jQuery 关于ajaxfileupload.js插件的逐步解析(ajaxfileupload.js第二弹)

licqi阅读(300)

如果你看了上一篇《ASP.NET 使用js插件出现上传较大文件失败的解决方法(ajaxfileupload.js第一弹)》的话,应该就知道我是逼不得已要认真学习下ajaxfileupload.js这个上传文件插件的。哈哈,开个玩笑啦,其实学习是给自己学的,而且学会了真的是很享受的~

这篇呢,就是想把这个插件的思路说一下,其中中文注解是我写的,英文注解应该是原作者写的吧~说实话,有些if判断里的东西我也没太弄明白,但是大致思路还是OK的。

jQuery.extend({


    createUploadIframe: function (id, uri) {//id为当前系统时间字符串,uri是外部传入的json对象的一个参数
        //create frame
        var frameId = 'jUploadFrame' + id; //给iframe添加一个独一无二的id
        var iframeHtml = '<iframe id="' + frameId + '" name="' + frameId + '" style="position:absolute; top:-9999px; left:-9999px" ';="" 创建iframe元素="" if="" (window.activexobject)="" {="" 判断浏览器是否支持activex控件="" (typeof="" uri="=" 'boolean')="" iframehtml="" +=" src="" 'javascript:false'="" '"';="" }="" else="" 'string')="" ;="" jquery(iframehtml).appendto(document.body);="" 将动态iframe追加到body中="" return="" jquery('#'="" frameid).get(0);="" 返回iframe对象="" },="" createuploadform:="" function="" (id,="" fileelementid,="" data)="" id为当前系统时间字符串,fileelementid为页面的id,data的值需要根据传入json的键来决定
        //create form    
        var formId = 'jUploadForm' + id; //给form添加一个独一无二的id
        var fileId = 'jUploadFile' + id; //给添加一个独一无二的id
        var form = jQuery('
'); //创建form元素 if (data) {//通常为false for (var i in data) { jQuery('').appendTo(form); //根据data的内容,创建隐藏域,这部分我还不知道是什么时候用到。估计是传入json的时候,如果默认传一些参数的话要用到。 } } var oldElement = jQuery('#' + fileElementId); //得到页面中的对象 var newElement = jQuery(oldElement).clone(); //克隆页面中的对象 jQuery(oldElement).attr('id', fileId); //修改原对象的id jQuery(oldElement).before(newElement); //在原对象前插入克隆对象 jQuery(oldElement).appendTo(form); //把原对象插入到动态form的结尾处 //set attributes jQuery(form).css('position', 'absolute'); //给动态form添加样式,使其浮动起来, jQuery(form).css('top', '-1200px'); jQuery(form).css('left', '-1200px'); jQuery(form).appendTo('body'); //把动态form插入到body中 return form; }, ajaxFileUpload: function (s) {//这里s是个json对象,传入一些ajax的参数 // TODO introduce global settings, allowing the client to modify them for all requests, not only timeout s = jQuery.extend({}, jQuery.ajaxSettings, s); //此时的s对象是由jQuery.ajaxSettings和原s对象扩展后的对象 var id = new Date().getTime(); //取当前系统时间,目的是得到一个独一无二的数字 var form = jQuery.createUploadForm(id, s.fileElementId, (typeof (s.data) == 'undefined' ? false : s.data)); //创建动态form var io = jQuery.createUploadIframe(id, s.secureuri); //创建动态iframe var frameId = 'jUploadFrame' + id; //动态iframe的id var formId = 'jUploadForm' + id; //动态form的id // Watch for a new set of requests if (s.global && !jQuery.active++) {//当jQuery开始一个ajax请求时发生 jQuery.event.trigger("ajaxStart"); //触发ajaxStart方法 } var requestDone = false; //请求完成标志 // Create the request object var xml = {}; if (s.global) jQuery.event.trigger("ajaxSend", [xml, s]); //触发ajaxSend方法 // Wait for a response to come back var uploadCallback = function (isTimeout) {//回调函数 var io = document.getElementById(frameId); //得到iframe对象 try { if (io.contentWindow) {//动态iframe所在窗口对象是否存在 xml.responseText = io.contentWindow.document.body ? io.contentWindow.document.body.innerHTML : null; xml.responseXML = io.contentWindow.document.XMLDocument ? io.contentWindow.document.XMLDocument : io.contentWindow.document; } else if (io.contentDocument) {//动态iframe的文档对象是否存在 xml.responseText = io.contentDocument.document.body ? io.contentDocument.document.body.innerHTML : null; xml.responseXML = io.contentDocument.document.XMLDocument ? io.contentDocument.document.XMLDocument : io.contentDocument.document; } } catch (e) { jQuery.handleError(s, xml, null, e); } if (xml || isTimeout == "timeout") {//xml变量被赋值或者isTimeout == "timeout"都表示请求发出,并且有响应 requestDone = true; //请求完成 var status; try { status = isTimeout != "timeout" ? "success" : "error"; //如果不是“超时”,表示请求成功 // Make sure that the request was successful or notmodified if (status != "error") { // process the data (runs the xml through httpData regardless of callback) var data = jQuery.uploadHttpData(xml, s.dataType); //根据传送的type类型,返回json对象,此时返回的data就是后台操作后的返回结果 // If a local callback was specified, fire it and pass it the data if (s.success) s.success(data, status); //执行上传成功的操作 // Fire the global callback if (s.global) jQuery.event.trigger("ajaxSuccess", [xml, s]); } else jQuery.handleError(s, xml, status); } catch (e) { status = "error"; jQuery.handleError(s, xml, status, e); } // The request was completed if (s.global) jQuery.event.trigger("ajaxComplete", [xml, s]); // Handle the global AJAX counter if (s.global && ! --jQuery.active) jQuery.event.trigger("ajaxStop"); // Process result if (s.complete) s.complete(xml, status); jQuery(io).unbind();//移除iframe的事件处理程序 setTimeout(function () {//设置超时时间 try { jQuery(io).remove();//移除动态iframe jQuery(form).remove();//移除动态form } catch (e) { jQuery.handleError(s, xml, null, e); } }, 100) xml = null } } // Timeout checker if (s.timeout > 0) {//超时检测 setTimeout(function () { // Check to see if the request is still happening if (!requestDone) uploadCallback("timeout");//如果请求仍未完成,就发送超时信号 }, s.timeout); } try { var form = jQuery('#' + formId); jQuery(form).attr('action', s.url);//传入的ajax页面导向url jQuery(form).attr('method', 'POST');//设置提交表单方式 jQuery(form).attr('target', frameId);//返回的目标iframe,就是创建的动态iframe if (form.encoding) {//选择编码方式 jQuery(form).attr('encoding', 'multipart/form-data'); } else { jQuery(form).attr('enctype', 'multipart/form-data'); } jQuery(form).submit();//提交form表单 } catch (e) { jQuery.handleError(s, xml, null, e); } jQuery('#' + frameId).load(uploadCallback); //ajax 请求从服务器加载数据,同时传入回调函数 return { abort: function () { } }; }, uploadHttpData: function (r, type) { var data = !type; data = type == "xml" || data ? r.responseXML : r.responseText; // If the type is "script", eval it in global context if (type == "script") jQuery.globalEval(data); // Get the JavaScript object, if JSON is used. if (type == "json") eval("data = " + data); // evaluate scripts within html if (type == "html") jQuery("
").html(data).evalScripts(); return data; } })

ajaxfileupload.js插件大致的思路就是如上所述,但是对于ajax来说,传值也是相当关键的部分,也就是传入的json对象里的键值对。

调用方法如下:

$.ajaxFileUpload
(
    {
        url: '../../XXXX/XXXX.aspx', //用于文件上传的服务器端请求地址
        secureuri: false,           //一般设置为false
        fileElementId: $("input#xxx").attr("id"), //文件上传控件的id属性   注意,这里一定要有name值   
                                                //$("form").serialize(),表单序列化。指把所有元素的ID,NAME 等全部发过去
        dataType: 'json',//返回值类型 一般设置为json
        complete: function () {//只要完成即执行,最后执行
        },
        success: function (data, status)  //服务器成功响应处理函数
        {
            if (typeof (data.error) != 'undefined'