`
yiminghe
  • 浏览: 1466291 次
  • 性别: Icon_minigender_1
  • 来自: 上海
社区版块
存档分类
最新评论

caja 原理 : 前端

 
阅读更多

    作为前端开放的基础安全保证,caja 是目前比较合适的运行机制,包括前端运行环境以及后端编译环境,这次先整体介绍下 caja 在前端是如何屏蔽外部模块代码对整体应用的影响 (注意:官方文档较少,以下为自己理解,难免偏颇).

组成部分

1. 整体运行环境:隔离模块与宿主环境,并提供外部应用与模块的沟通机制.

 

2. 提供 ecmascript5 以及 dom2 的全平台兼容实现,注入到运行环境中。

 

     es5 部分通过直接修改本地原生对象原型实现,运行时直接使用原生对象,这种做法值得推荐

 

      其中比较重要的是:模拟实现 es5 中的属性描述符 ,结合后端编译,用户的所有读写操作都会经过 caja 前端运行环境监测,是 caja 安全保证的核心机制.

 

     dom 兼容部分则并不是直接修改宿主 dom 原型 ,而是自行构造了一系列 javascript 实现的 dom 类,使用组合模式,将操作增强并委托到对应的原生节点,然后将这些 dom 类注入到模块运行环境。用户程序对 dom 节点的操作都要经过 caja 运行环境的转发,便于控制。

 

3. html/css parser ,包含了简易的 html/css parser,对用户的 html,css进行必要的过滤以及添加自定义规则,最常见的是

 

 1. 防止 id 冲突,经过过滤后,每个 id 都改写为全局唯一的标志.

 

 2. 拦截随意跳转,监控代码的可能跳出点( src , href ...).

 

 3. 代码模块化机制,后端将用户代码编译为模块化单元,前端通过模块化机制加载并初始化用户代码。实现代码广泛采用了 promise .

运行机制

caja 中的每个模块表示为一段 html,css,javascript 的结合体,外部应用嵌入多个模块,caja 保证每个模块的独立性与安全性:

 

1.不能访问平台的相关特性.(window.location,cookie )

2.不能污染全局。( 全局变量,原生对象,宿主对象 )

3.节点操作限于模块内部.

 

运行过程中,caja 的整体运行环境会对每个将要加载的模块创建一个 runtime iframe,每个runtime iframe 都加载了 es5 兼容环境脚本以及 caja 模块机制脚本,由模块机制脚本加载对应的编译后用户模块代码并运行.

 

 

除了一个模块一个 runtime iframe,所有模块都共享一个 tame iframe ,该 tame iframe 加载 dom 兼容层脚本,caja 运行环境负责调用 tame iframe 为每个 runtime iframe 建立一个以模块根节点为 body 的虚假的 dom 环境,再将该环境融入到 runtime iframe 中,因此所有模块的 dom 节点类都是一样的,环境都是从tame iframe中产生的拥有不同 body 节点的不同实例.

 

 

一些讨论点

1. 由于 caja 中对所有的原生 dom 都组合包装了一层兼容实现,但最终运行都要转交给原生的 dom 节点,而不同模块间原生 dom 节点本来就共享一个宿主环境,因此 dom 兼容在所有模块间共享是合适的.

 

2. 每个模块都有自己的运行环境,存放虚假 dom 容器以及 es5 环境,通过每个模块运行在一个单独的 iframe,那么可以将该环境放在对应模块的 iframe 中,每个用户模块代码可以在自己的 iframe 内对一个统一环境变量进行操作,不需要模块间区分。

 

3.外部应用与模块间沟通通过 tame iframe 进行,外部应用数据 tame 后进入模块内执行,模块数据 untame 后回到外部应用,确保模块内都是包装过的内容,用户代码不能直接访问到原生数据.

示例

用户模块代码

<style type="text/css">
    #xx {
        color: red !important;
    }
</style>

<span id='xx'></span>
<script type="text/javascript">
    document.getElementById("xx").innerHTML='wow!';
</script>

 

编译后代码

 

___.loadModule({
    // 模块启动
    'instantiate': function (___, IMPORTS___) {
        // html 子模块
        return ___.prepareModule({
            'instantiate': function (___, IMPORTS___) {
                var dis___ = IMPORTS___;
                var moduleResult___;
                moduleResult___ = ___.NO_RESULT; {
                    // html parser 必要修改   
                    IMPORTS___.htmlEmitter___.emitStatic('\x3cspan id=\"id_2___\"\x3e\x3c/span\x3e');
                }
                return moduleResult___;
            },           
        }).instantiate___(___, IMPORTS___),
        // css 模块
        ___.prepareModule({
            'instantiate': function (___, IMPORTS___) {
                var dis___ = IMPORTS___;
                var moduleResult___, el___, emitter___;
                moduleResult___ = ___.NO_RESULT; {
                    // css parser 必要修改
                    IMPORTS___.emitCss___(['.', ' #xx-', ' {\n  color: red !important\n}'].join(IMPORTS___.getIdClass___()));
                    emitter___ = IMPORTS___.htmlEmitter___;
                    el___ = emitter___.byId('id_2___');
                    emitter___.setAttr(el___, 'id', 'xx-' + IMPORTS___.getIdClass___());
                    el___ = emitter___.finish();
                }
                return moduleResult___;
            },
        }).instantiate___(___, IMPORTS___),
       // javascript 子模块
        ___.prepareModule({
            'instantiate': function (___, IMPORTS___) {
                var dis___ = IMPORTS___;
                var moduleResult___, x0___, x1___;
                moduleResult___ = ___.NO_RESULT;
                try {
                    {
                        // 从运行环境中取出必要数据运行
                        moduleResult___ = (x1___ = (x0___ = IMPORTS___.document_v___ ? IMPORTS___.document : ___.ri(IMPORTS___, 'document'), x0___.getElementById_m___ ? x0___.getElementById('xx') : x0___.m___('getElementById', ['xx'])), x1___.innerHTML_w___ === x1___ ? (x1___.innerHTML = 'wow!') : x1___.w___('innerHTML', 'wow!'));
                    }
                } catch(ex___) {}
                return moduleResult___;
            },

 

上述代码运行于各自模块的 runtime iframe ,其中 IMPORTS__ 即为对应模块的运行环境,外部应用也可导入一部分 api 到此环境。

 

嵌入代码

 

最终动态嵌入到主应用的代码为:

 

 

<style type="text/css">.CajaGadget-0___ #xx-CajaGadget-0___ {
  color: red !important
}</style>

<div title="&lt;Untrusted Content Title&gt;" class="caja_innerContainer___ CajaGadget-0___ vdoc-body___"><span id="xx-CajaGadget-0___">wow!</span></div>
 

可见经过 html/css parse 后避免了 id 冲突,而 js 加载后直接执行掉了,页面上并不存在。

 

 

错误处理

 

错误处理分两类:

 

1. 初始化错误

 

caja 会把模块初始化代码 try catch 起来交由用户配置的 onerror 函数处理,这样一个好处是,可以判断出当时出错时那个模块,例如:

 

 frameGroup.makeES5Frame(document.getElementById("xx"), function (frame) {
     var onerror = frameGroup.tame(frameGroup.markFunction(function (message, source, lineNum) {
         console.log('初始化出错啦: ' + message + ' in source: "' + source + '" at line: ' + lineNum);
         return false;
     }));

     frame.url("yy.html")
         .run({
         onerror: onerror
     });
 });

  2. 异步错误

 

异步错误是指初始化以外由事件或异步请求处理导致的错误,这种情况下实际上脱离了 caja 的工作范围,由于编译后程序在主窗口js引擎运行,那么错误会直接冒泡到主窗口的 window.onerror 事件处理器,而这时就区分不出来是哪个模块发生错误了:

 

window.onerror=function(message, source, lineNum){
                console.log('运行中出错啦: ' + message +
                                                ' in source: "' + source +
                                                '" at line: ' + lineNum);
            };

 frameGroup.makeES5Frame(document.getElementById("xx"), function (frame) {
     var onerror = frameGroup.tame(frameGroup.markFunction(function (message, source, lineNum) {
         console.log('初始化出错啦: ' + message + ' in source: "' + source + '" at line: ' + lineNum);
         return false;
     }));

     frame.url("yy.html")
         .run({
         onerror: onerror
     });
 });

 

 

期望后期 caja 对所有的函数调用进行 try catch,这样才能得到错误的具体模块以及行号。

 

目前的一个暂时解决方法为,对于事件调用都异步操作提供定制 api,在这个 api 里对用户的调用进行封装

 

frame.url("x.html")
    .run({
    onerror: onerror,
    KISSY: shared({
        onerror: function (e) {
            console.log('x.html 运行中出错啦: ' + e.message);
        },
        imports: frame.imports,
        context: document.getElementById("theGadget")
    })
});

 

在 share 中返回事件注册新的 api,并对 用户调用函数进行 try catch:

 

function shared(param) {
    function genWrapper() {
        function wrapper(e) {
            if (e.target) {
                e.target = tame(e.target);
            }
            if (e.relatedTarget) {
                e.relatedTarget = tame(e.relatedTarget);
            }
            if (e.currentTarget) {
                e.currentTarget = tame(e.currentTarget);
            }
            // 对用户真正的处理函数进行 try catch
            try {
                return wrapper.handle.call(this, e);
            } catch (e) {
                if (param.onerror) {
                    param.onerror(e);
                } else {
                    throw e;
                }
            }
        }
        return wrapper;
    }
    return frameGroup.tame({
        Event: {
            add: frameGroup.markFunction(function (s, event, handle, scope) {
                var wrapper = genWrapper();
                wrapper.handle = handle;
                handle.__event_tag = handle.__event_tag || [];
                var els = query(s);
                S.each(els, function (el) {
                    handle.__event_tag.push({
                        fn: wrapper,
                        el: el,
                        scope: scope || el
                    });
                });
                S.Event.on(els, event, wrapper, scope);
            });,
        }
    });
}

 

此时用户在模块代码里通过:

 

KISSY.Event.on('.xx',function(){
  throw new Error('xx error!');
});
 

注册的事件处理函数里抛出的错误可被对应模块捕获而不会到顶层 window.onerror 。

 

PPT

Caja "Ka-ha" Introduction

  • 大小: 35.4 KB
分享到:
评论
2 楼 317966578 2014-11-04  
兄弟我最近也在整jquery和caja 开放一些接口。在github上也看到了你们的caja_kissy的项目关于jquery开放接口的。现在我们的需求是不用你们淘宝的kissy。直接通过caja开放一些jquery的接口。目前我已经实现了选择器接口的访问,但是模块里面的js通过jquery开放的接口同时也访问到了host page(主页面)的DOM对象。帮忙看看怎么解决这个问题,是不是我的caja 封装的jquery 接口写法有问题。host page主页面的代码如下

<html>
  <head>
    <title>Caja host page</title>
    <script type="text/javascript"
            src="http://localhost:8080/caja.js">
    </script>
<script type="text/javascript"
            src="http://localhost/jquery-1.6.4.js">
    </script>
  </head>

  <body>
    <h1>Caja host page</h1>
<div id="myoutDiv" class="myoutdivClass"></div>
<div class="myDiv"></div>
    <div id="guest" >
<div id="dynamicContent" class="dinamicContent"></div>
</div>

    <script type="text/javascript">

    var $ = window.jQuery;
   
     function init() {

        function tameNode(node){
            //方法占位,frame对象此时没有还
        }

        function SafejQuery(selector) {
            this.inner = $(selector);
        }

        //为我们‘继承'的构造函数添加需要开放给外部使用的原型方法
        SafejQuery.prototype.add = function () {
            var p = arguments[0];

            this.inner.add(arguments)
            return this;
        };
        SafejQuery.prototype.addClass = function () {
            this.inner.addClass(arguments[0]);
            return this;
        };
        SafejQuery.prototype.removeClass = function () {
            this.inner.removeClass(arguments[0]);
            return this;
        };
SafejQuery.prototype.html = function () {
            this.inner.html(arguments[0]);
            return this;
        };
        SafejQuery.prototype.each = function () {
            var fn = arguments[0];
            this.inner.each(caja.markFunction(function(index,element){
                fn(index,tameNode(element));
            }));
            return this;
        };

        //---- 组件是一个构造函数进行初始化的,需要markCtor标记一下,让caja容器认识
        caja.markCtor(SafejQuery);

        //构造函数实例的方法,需要grantMethod ,加入白名单,没有加入白名单的不可以使用,caja容器不认识
        caja.grantMethod(SafejQuery.prototype, "addClass");
        caja.grantMethod(SafejQuery.prototype, "removeClass");
        caja.grantMethod(SafejQuery.prototype, "each");
caja.grantMethod(SafejQuery.prototype, "html");

        /**
         * @param context 上下文
         * @param context.mod 沙箱的模块范围,所有操作必须限定到模块范围之内去执行
         * @param context.frame 单个模块的沙箱
         * @return {Object} 实际的组件对象
         */
        return function (context) {

            tameNode = function(n){
               return context.frame.imports.tameNode___(n, true);
            }

            //最终需要返回给
            return {
                jQuery: caja.tame(caja.markFunction(function () {
                    return new SafejQuery(arguments[0]);
                }))
            }
        }

    }


     var uriPolicy = {
          rewrite: function(uri) {
        var valid = /.*(\.yhd\.com)$/.test(uri);
            if (valid) {
                return uri;
             }
            return undefined;
          }
        };


      function alertPop(x){
alert(x);
      }

      caja.initialize({
cajaServer: 'http://localhost:8080/',
debug: true,
es5Mode: true
});

       var tameAlert;
       var tameSafeJquery;
       var tameGetoutDiv;
       caja.whenReady(function(){
var tameObje = init()();
tameSafeJquery = tameObje.jQuery;
caja.markFunction(alertPop);
tameAlert = caja.tame(alertPop);


});

caja.load(document.getElementById('guest'), uriPolicy, function(frame) {
frame.api({
pop:tameAlert,
$:tameSafeJquery
});

      frame.code('http://localhost:80/test.js','application/javascript').run(function(result){
   alert(result);
});
});

test.js  第三方js 如下

  pop("111");
  pop(getOut());
  var findObj = $(".myoutdivClass");//外部的div被访问到了
  findObj.html("this is jquery test");
1 楼 meteoric_cry 2011-09-03  
不错,支持一下

相关推荐

    caja-extensions:Caja扩展(MATE文件管理器)

    **caja-extensions:深入理解MATE文件管理器的扩展功能** Caja是MATE桌面环境中的默认文件管理器,它的设计目标是提供一个简洁、高效且用户友好的界面来管理文件和目录。Caja-extensions是针对Caja的一个开源项目,...

    caja-repairer:用于文件名编码修复的 Caja 扩展

    【标题】:“caja-repairer:用于文件名编码修复的 Caja 扩展” 【描述】中的“caja-repairer”是一个专门设计用于修复文件名编码问题的扩展,它与Caja项目关联,Caja是一个开源的、强大的文件管理器,常用于Linux...

    caja:Caja是一种用于在您的网站中安全嵌入第三方HTML,CSS和JavaScript的工具

    Caja使用对象能力安全模型来允许广泛的灵活安全策略,以便您的网站可以有效地控制嵌入式第三方代码可以对用户数据执行的操作。 Caja支持大多数HTML和CSS以及最近标准化JavaScript“严格模式” JavaScript版本-即使...

    caja-menu.zip

    【caja-menu.zip】是一个与Linux桌面环境相关的压缩文件,主要包含了一个名为"caja-menu"的扩展,它被设计为增强Caja文件管理器的功能。Caja是MATE桌面环境中的核心组件,是一个轻量级且用户友好的文件管理工具,...

    caja-spec

    《caja-spec》是关于Caja项目的详细技术规范文档,主要关注的是互联网应用程序的安全性和脚本语言的沙箱环境。Caja项目是由Google发起的一项创新性工程,旨在为Web开发提供一个安全的、可隔离的JavaScript执行环境,...

    caja:棋盘游戏收集经理

    "caja:棋盘游戏收集经理"是一款专为棋盘游戏爱好者设计的管理软件,它利用JavaScript技术构建,旨在帮助用户高效地整理、管理和分享他们的棋盘游戏收藏。这款应用可能包含了诸如游戏信息录入、收藏品分类、库存管理...

    前端工程师.pdf

    - Caja:Caja是一个项目,旨在让网站安全地嵌入第三方DHTML web应用,并允许嵌入应用与宿主页面之间进行丰富的互动。它使用对象能力安全模型,为实施灵活的安全策略提供可能,保护用户数据免受恶意应用的滥用,并...

    caja-actions

    Caja-Actions具有三种组件: 用户界面caja-actions-config-tool(CACT),可用于管理您的操作。 使用CACT,您可以创建,修改和删除操作,定义菜单和子菜单,对项目进行排序和重新排序。 导入/导出功能也通过CACT...

    前端开源库-jpm

    Jetpack框架包含了一系列模块,如Add-on SDK、Caja、jQuery等,这些模块提供了丰富的功能,如DOM操作、网络请求、数据存储等。 **三、jpm主要功能** 1. **创建项目**:jpm提供了一个简单的命令行选项,可以快速...

    前端xss防火墙

    6. **XSS过滤库**:利用现有的XSS过滤库,如Google的Caja或DOMPurify,它们提供了现成的解决方案,可以帮助开发者快速构建安全的前端环境。 7. **安全API使用**:谨慎使用可能引入安全风险的API,如`eval()`和`...

    Modelos3D:Caja de modelos 3D

    在构建3D模型展示平台时,HTML通常与CSS(层叠样式表)和JavaScript一起使用,形成前端开发的三位一体技术栈。HTML负责页面的基本布局和内容组织,CSS则用于美化和定制页面样式,而JavaScript则用于实现交互性和动态...

    CajaRegistradoraJava:Java的Desarrollo de Caja注册商标

    在IT行业中,"CajaRegistradoraJava"是一个项目名称,它涉及到使用Java编程语言来开发一款收银机软件,即“Caja注册商标”。这个项目的核心是创建一个功能完备、用户友好的商业收银系统,适用于各种零售环境,如超市...

    caja-r4314.jar

    jar包,亲测可用

    caja-r4251.jar

    jar包,亲测可用

    toolbox:Caja de herramientas varias para C

    "toolbox:Caja de herramientas varias para C"是一个针对C语言的综合工具箱,它提供了多种实用的函数和库,旨在帮助C程序员更高效地开发、调试和优化代码。这个工具箱可能包含了各种实用的模块,如内存管理工具、...

    修改后的gtkrc文件

    解决ubuntu12.10下unity桌面中,eclipse提示颜色问题,具体方法见:http://blog.csdn.net/on_way_/article/details/8566904

    Caja_them_vb6_

    311/5000Cashier module. It allows to register the sales of a SPA. Save the detail of the services that were performed who performed them and the corresponding fee slip for each of them.At the end he ...

    auto-sanitize:google-caja 的实现,有助于通过一次函数调用来清理对象

    自动消毒google-caja 的实现,有助于通过一次函数调用来清理对象安装 npm install auto-sanitize --save用法 // 'util' is just for you to see the contents of// the objects, we don't actually need it to ...

    AgilePoint bridges the .NET BPM Divide

    Caja Duero是一家金融机构,利用AgilePoint BPM Suite开发了符合ISO 20000标准的自动化工作流程,以管理和监控其IT服务管理流程。 ##### 三、市场影响力与客户基础 AgilePoint在电信和技术市场上具有强大的市场...

    Product_Card-modelo-de-caja-

    标题 "Product_Card-modelo-de-caja-" 暗示我们正在讨论一个产品卡片的模型,这通常在网页设计中用于展示商品或服务的基本信息。产品卡片是网站中常见的组件,尤其是在电商网站、应用商店或者内容目录中。它们提供了...

Global site tag (gtag.js) - Google Analytics