内容


网络中的 Ajax

在 Ajax 架构中聚合来自多个站点的内容所面临的安全性和拓扑问题

使用 WebSphere Application Server Feature Pack for Web 2.0 和 Tivoli Access Manager WebSEAL

Comments

系列内容:

此内容是该系列 # 部分中的第 # 部分: 网络中的 Ajax

敬请期待该系列的后续内容。

此内容是该系列的一部分:网络中的 Ajax

敬请期待该系列的后续内容。

简介

Ajax 架构一个让人兴奋的特性就是能够聚合来自多个数据源的内容并由此创建一个全新的站点或 Web 应用程序。比方说,您可以创建这样一个 Web 应用程序,它能综合各种气象服务的信息来给出几个滑雪场的天气信息。这些天气信息原来可以通过几个 Web 站点得到,而您的应用程序将这些数据一同带到了一个 Web 页上。这类应用程序通常都被称为 mashup。无需深入探究,只借助 Google 或 Yahoo,就能充分领略在创建这类 Web 应用程序方面的诸多创新的可能性。

创建一个网络拓扑来支持来自多个站点的内容聚合,需要面对很多技术挑战。这些技术挑战包括浏览器的跨域限制、可能的用户会话到期失效、持久连接超时以及可能的身份验证和授权问题。本文着眼于与 Ajax 有关的这些技术挑战并探究了如何综合使用 IBM WebSphere Application Server Feature Pack for Web 2.0 以及 IBM Tivoli Access Manager WebSEAL 来解决这些技术挑战。

Ajax 拓扑

图 1 显示了一种典型的 Ajax 风格的架构。在左侧是一个客户机,比如一个 Web 浏览器。在这个例子中,浏览器支持 JavaScript 编程语言,用来处理被浏览页面的 Document Object Model (DOM)。这些页面可能会包含 JavaScript 小部件,这些小部件执行一个 GUI 函数。比方说,您可能会创建一个能从服务器拉出数据并在浏览器上显示内容的小部件。这个小部件负责 DOM 的创建和操纵,而浏览器则使用这个 DOM 来显示图形内容。

图 1. 使用代理的 Ajax 架构
图 1. 使用代理的 Ajax 架构
图 1. 使用代理的 Ajax 架构

图 1 的中心是一个前向代理。若想聚合来自多个 Web 服务的内容或需要在连接到网络的那些客户机上强制施行一种安全机制,那么代理对于 Ajax 架构而言就愈发重要。比方说,Ajax 极大地依赖于 XMLHTTPRequests 在后台发出网络连接以便检索数据。按照设计,现代的浏览器不允许 XMLHTTPRequests 到达所记录的起源之外的那些域。比如,如果您创建的 JavaScript GUI 小部件源自 http://www.mysite.com,但随后向 http://www.mydata.com/data 发出一个 XMLHTTPRequest 来拉出数据,该请求就会被浏览器阻塞。与之相反,客户机会连接到充当中介的代理,并将客户机的请求安排到其他的域。从客户机的角度看,请求像是源自于与原始文档相同的域。除了处理浏览器自己的安全模型之外,代理还可以提供额外的一层身份验证和授权以便充当访问网络上的文档或服务的一个控制点。

图 1 的右侧是基于浏览器的客户机可能试图访问的各种服务或文档端点。在 Ajax 架构中,这些服务端点可能会访问一个数据库、Enterprise Service Bus 或其他后端服务来拉出信息,所返回的这些信息的格式要对客户端点可用。典型的例子包括 SOAP、ATOM、XML 或 JSON。

组合在一起

IBM WebSphere Application Server Feature Pack for Web 2.0 是 WebSphere Application Server Versions 6.0、6.1 和 7.0 的一个可选插件。此外,它还支持 IBM WebSphere Application Server Community Edition V2.x。这个功能部件包包含客户端和服务器端技术,可被用来创建 Ajax 风格的架构。这种技术以库的形式提供,可被包含到您的 Web 应用程序。作为一个开发人员,您可以选择适合于您想要创建的 Ajax Web 应用程序的那些库包。下面的列表总结了在 Web 2.0 功能部件包中可用的一些特性。

客户端

  • Dojo 1.1 Toolkit 是一个 JavaScript 工具箱,可简化使用 JavaScript 创建 Ajax 应用程序的过程。
  • IBM 对 Dojo 的扩展
    • 用来处理 SOAP 或 Atom feed 内容的 JavaScript 库。
    • Gauge GUI 小部件
    • OpenSearch 库

服务器端

  • 一个基于 Java 的 JSON 库,用来创建可被基于 JavaScript 的客户机轻松使用的 JSON 数据。
  • 基于 Abdera 的 feed 库,可被用来创建 ATOM feed。
  • 一个基于 servlet 的 Ajax 代理,可被嵌入到您的应用程序。
  • Ajax 消息处理,被用来创建 Comet 风格的架构。

IBM Tivoli Access Manager WebSEAL 是一个逆向的 Web 代理,可从一个基于浏览器的客户机接收 HTTP/HTTPS 请求并发布来自其自身的 Web 服务器的内容或者访问后端的 Web 应用服务器,比如 WebSphere Application Server。经由 WebSEAL 传递的请求被评估,以决定用户是否有权访问所请求的资源。WebSEAL 支持如下特性:

  • 可插式架构,支持多种身份验证机制。
  • 可接受 HTTP 以及 HTTPS 请求。
  • 通过为本地和后端服务器 Web 空间提供细颗度的访问控制来保护后端服务器资源。
  • 充当逆向 Web 代理。
  • 提供单点登录功能。
  • 能很好地与一些第三方软件产品集成。

图 2 给出了修改后的拓扑图,并展示了如何联合利用 Web 2.0 功能部件包和 WebSEAL 来得到一个基于 Web 的 Ajax 解决方案。WebSEAL 通过提供代理和安全服务形成了网络拓扑的基础,而 Web 2.0 功能部件包则提供了应用程序库来创建 Ajax 风格的 Web 应用程序。

图 2. 更新后的拓扑,使用 WebSEAL 作为代理
图 2. 更新后的拓扑,使用 WebSEAL 作为代理
图 2. 更新后的拓扑,使用 WebSEAL 作为代理

在客户端,Web 2.0 功能部件包提供了 Dojo 1.1 JavaScript 工具箱。Dojo Toolkit 是一种开源解决方案,可被用来创建 Ajax 风格的架构。Dojo Toolkit 可分组为如下三个组件:

  • Core 包含一个压缩 JavaScript 库,可提供框架,Dojo 的其他组件在这个框架上构建。事件、打包、基于 CSS 的查询、动画、JSON 处理等均具备框架支持。Core 之上是 Dijit。
  • Dijit 是一组可定制的模板驱动的小部件,可支持本地化和可访问性。Dijit 包含一个非常丰富的小部件集,可开箱即用,也可根据需要定制。除 Dijit 外,是 Dojox。
  • Dojox 包含一些创新组件,比如制图、离线支持、网格等。

此外,IBM 还提供了具有 Web 2.0 功能部件包的 JavaScript 库,用来处理 SOAP 和 ATOM 格式,此外,还提供了基于 GUI 的小部件,来创建模拟风格的器具,比如一个仪表盘。

图 2 的中心展示的是 Tivoli 的 WebSEAL 产品,该产品充当代理的角色并提供安全机制来控制对网络中的文档以及 Web 对象的访问。WebSEAL 服务器和后端 Web 应用服务器之间的连接被称为是一个 WebSEAL 联结。一个 WebSEAL 联结可以被视作是 WebSEAL Web 空间内的一个逻辑安装点,如图 3 所示。一个联结创建了 Web 文档的逻辑表示并使得 WebSEAL 得以代表 WebSEAL 所连接到的后端服务器来提供安全性。由于 WebSEAL 联结创建了后端应用服务器的 Web 空间间的逻辑映射,因而联结可被用来为用户或 Web 应用程序创建一个透明映射。比如,如果您所创建的 Web 应用程序源自 http://www.mysite.com,但却从 http://www.yoursite.com/jsondata 访问客户数据,那么您就可以创建一个 WebSEAL 联结,来将访问映射到 http://www.mysite.com/junction/jsondata。WebSEAL 会将请求无缝映射到 http://www.yoursite.com/jsondata 并将数据返回给客户机。

图 3. 具有 Tivoli WebSEAL 联结的拓扑
图 3. 具有 Tivoli WebSEAL 联结的拓扑
图 3. 具有 Tivoli WebSEAL 联结的拓扑

如果服务器要求对一个目录要有细粒度的安全性,那么就需要在 WebSEAL 上创建一个联结来识别需要额外身份验证或授权的 Web 对象或文档。Tivoli Access Manager 为 WebSEAL 提供了这样的安全性框架。

WebSEAL 接收来自客户机的一个 URL 请求,并且基于联结配置,将会向服务器发出一个请求。从服务器的角度,WebSEAL 表面看上去就如同是另一个客户机请求信息。根据所返回的数据,服务器将数据格式化为合适的协议,比如 JSON、ATOM、SOAP 或 XML。根据所返回的协议,Web 2.0 功能部件包提供了应用程序服务器端技术,并进而简化了数据内容的返回:

  • JSON4J 是一个 Java 库,可被用在应用程序内创建 JSON 格式的数据流。例如,您可能需要从一个 SQL 数据库返回某个表内的列。JSON4J 可被用来将数据格式化为一个 JSON 数据流,它之后再被返回给客户机。
  • Abdera 0.40 自带 Web 2.0 功能部件包,可用来创建来自服务器的 ATOM 风格的发布 feed。这些 feed 可以代表新闻信息或任何其他需要发布的内容。
  • RPCAdapter 让您能够使用 JavaScript 调用 Java 方法。尤其是,您可以在客户机上创建 一个 JSON 数据流,其中包含要调用的各种方法及参数。数据被发送到 RPCAdapter,RPCAdapter 根据 JSON 请求调用所指定的方法。方法的响应可以是一个 Java Bean,被序列化为一个 XML 或 JSON 数据流后返回给此客户机。有了 RPCAdapter,将遗留 Java Beans 映射为可供 Ajax 客户机消费的格式就变得较为简单。

部署 Ajax 架构时所面临的几个挑战

根据应用程序和复杂性,在向网络中部署一个 Ajax 风格的架构时,需要提前回答很多问题。如下是一些例子:

用户会话到期失效

使用上述架构,考虑这样一个场景,您已经创建了一个 Dojo JavaScript 小部件,该小部件向服务器请求数据以便在表中显示。这个小部件使用 Dojo 的 xhrGet 函数来向服务器发出一个 XMLHTTPRequest 。代理将请求传递给服务器,服务器用一个 JSON 数据流进行响应,Dojo 小部件可轻松解析这个数据流并显示数据。当由于超时或超出了最大允许的会话数量 WebSEAL 代理会话超时时,又会如何呢?

从小部件的角度看,代码发出一个请求并期待返回的格式是 JSON。但是,小部件接收的到却是一个 HTML 响应,那是一个 Web 格式的登录页面。这个 Web 格式的登录页面请求用户 ID 和密码。通常,Web 登录并不是问题,但是由于对服务器的底层请求在幕后发生,所以没有明显的用户登录动作。图 4 展示了问题。

图 4. 用户会话到期失效
图 4. 用户会话到期失效
图 4. 用户会话到期失效

从 WebSEAL 的角度,当一个用户的会话到期失效时,它会缓存所发出的最后一个请求并返回 Tivoli Access Manager 登录表单。当登录表单提交后,WebSEAL 和用户重新验证后的 WebSEAL 重新提交此请求。在一个复杂的 Ajax 应用程序中,这通常会导致丢失大量的上下文。通过截获这种行为,您就可以记住这个上下文并努力恢复它来提供最优的用户体验。

可能的解决方案

这里的一个可能解决方案是识别没有接收正确数据并相应地进行响应的时间。在 JSON 数据的情况下,这里的关键是使用 dojo.fromJSON(data) 并解析 JSON 响应。如果您接收到来自表单登录的 HTML 或一个来自服务器的 HTML 错误页面,那么解析就会失败。如果出现问题,要捕获异常并加以处理。HTTP 错误条件代码可以由错误函数捕获。ioArgs 参数提供了额外的属性,可用来探究问题。

清单 1. 示例解决方案
<script>
        var timer = null;
        // Function that retrieves a JSON object and puts the information
        // into the div with an id of 'json-content'. Notice how we're defining
        // 'handleAs' to be of type 'text'. We we handle the parsing of the JSON
        // data so that we can trap the exception if it occurs.
        function getJson () {
            
            var d = dojo.xhrGet ({
                                  url:      '/webseal/login',
                                  handleAs: 'text', 
                                  load:     loadIntoNode,
                                  error:    errorCondition
                                 });
        };


          function loadIntoNode(data, ioArgs){
            try{
                console.log("Read JSON Data ... do something",data);
				
		// read the JSON data that was returned by the service 
                var jsonData = dojo.fromJson(data);

                console.log(jsonData.attribute);
                console.log(jsonData.number);
		
                //do something with the JSON data that you read
		//dojo.byId("json-content").innerHTML = data;

            }catch(e)
            {
                // respond with an error reading JSON.  As an example, if an HTML form
                // login is returned, then dojo.fromJson will throw an Exception. 
                // Catch the exception and dispaly a login box. At this point, we
                // are assuming the error condition is because Dojo can't parse the
                // JSON stream. You can fine tune the error condition by looking at
                // the e.message value
		        console.log("error reading json data ",e.message);
                
                // Do something on the error condition, display a login widget, or 
                // look at the ioArgs to further narrow the problem down.
          }

          function errorCondition(error,ioArgs){

           console.log("Status",ioArgs.xhr.status);
              
            // retrieve an error message for the HTTP response code.  As an 
            // example, if we get a 500 (Server Error) then take an action.

            switch(ioArgs.xhr.status) {
            case 404: //page not found error
                      break;
            case 500: // server side error
                      break;
            case 407: // proxy authentication
                      break;
            default: // default action
            };

            console.log("HTTP Error code:",ioArgs.xhr.status);
            console.log("Error Condition:",error.responseText);
	        console.log("Error Message:  ",error.message);
	        
	        //dojo.byId("json-content").innerHTML = ioArgs.xhr.status;
          };       
        
</script>

持久连接

保持客户机和 Web 服务器之间的持久连接正在成为 Ajax 环境中的一种十分流行的技术。比如,Web 2.0 功能部件包支持 Comet 编程模型。Comet 是一个 Web 应用程序模型,其中持久的 HTTP 连接可以让服务器在浏览器并未请求数据的情况下也能推出数据。由于到服务器的连接总是开放的,服务器可轻松地将数据推出到客户机。当超时连接到期失效时,这个 JavaScript 库就会重新打开对服务器的连接并一直保持打开,直到超时。此过程会重复进行,JavaScript 库再次打开连接。

库存报价应用程序就是一个典型的例子,在没有用户交互的情况下,不断更改的价格信息会被推出到浏览器。用户会看到变化经常发生。Comet 编程模型与轮询模型正好相反,在轮询模型中,客户机打开一个连接、请求更新,然后再次关闭连接。Comet 编程模型的结果就是持久连接将一直对客户机连接到的服务端点保持打开。图 5 展示了这个场景。

图 5. Comet 编程模型
图 5. Comet 编程模型
图 5. Comet 编程模型

对于 WebSEAL,这意味着除了从其他由用户发起的请求生成的一些较短的连接之外,具有激活会话的那些用户将一直保持打开至少一个对服务器的持久连接。这将消耗 WebSEAL 的工作线程,并且 [server] worker-threads 参数的值需要在 WebSEAL 配置文件内被调优。在具有大量激活用户会话的极端情况下 ,可能还需要添加 WebSEAL 实例。

可能的解决方案

判断在 WebSEAL 配置文件内配置的 [server] worker-threads 参数的当前设置是否有问题。最简单的方法是判断使用 WebSEAL 统计数据是否存在问题。特别地,要监视 pdweb.threads 组件的输出(清单 2)。

清单 2. pdweb.threads 组件的统计数据 get 命令的输出
#pdadmin> server task default-webseald-instance stats get pdweb.threads
active : 0 total : 50

如果所报告的 active 的值不断接近 total 的值,那么您需要让更多的 worker 线程可用。有关配置 WebSEAL worker 线程的更多信息,请参考 WebSEAL Performance Tuning Guide。有关收集 WebSEAL 统计数据的更多信息,请参考 Tivoli Access Manager for e-business Problem Determination Guide 以及 Tivoli Access Manager for e-business Auditing Guide。

身份验证和授权

根据所开发的 Web 应用程序,有可能会需要控制对 Web 文档的访问或者 Web 应用程序允许连接到的服务器的类型。虽然 Tivoli Access Manager 身份验证和授权架构(为 WebSEAL 所用),超出了本文的讨论范围,但是给出一个简短的描述无疑将有助于您理解创建涉及到 WebSEAL 的 Ajax 风格的架构时所面临的问题。

Tivoli Access Manager 包含一个安全策略,它定义了对域的访问。这个域由两个安全结构主导:用户注册表(比如 LDAP、Lotus Domino 或 Microsoft Active Directory)和一个授权策略数据库。用户注册表 包含被允许加入此域并完成身份验证的用户和组。授权策略数据库 包含域内所有资源的表示。安全管理员可以通过应用 protected object policies (POP) 和 access control lists (ACL) 规则来指定安全等级。一个受保护的对象可以是一个 Web 资源,比如一个页面或一个浏览器客户机试图连接到的到服务端点的 URL。

取决于 WebSEAL 的具体配置,通过验证或未通过验证的客户机都可以连接到一个安全域。只有在用户注册表内有相应条目的用户才能成为通过身份验证的用户。

WebSEAL 代理从 Dojo 小部件接收来自浏览器客户机的请求。从一个未经身份验证的客户机或权限低于所要求权限的帐户试图访问受保护的对象将会分别导致一个登录提示或 "HTTP 401" 错误。一个用户代理,比如浏览器,知道该如何处理这些场景;但是典型的使用 Ajax 作为请求机制的客户机并不知道。

链接重写和同源策略

对于一个标准的 WebSEAL 联结,只有在适当配置的情况下 JavaScript 过滤才会执行,即联结使用 -j 选项建立。

大多数的 Web 开发人员都会在其 JavaScript 代码中包含 URL 信息。这些 URL 信息不会被 WebSEAL 重写,因而会带来很多问题,并且大多围绕死链接。由于 JavaScript 强制施行同源策略,对于由 XMLHttpRequest 对象所做的请求,也存在特定的问题。

如下示例展示了一个 Web 开发人员一般是如何编写 Ajax 内容的。通过将 JavaScript 操作与 HTML 内容分离开来,资源得到了很好的重新使用。但是,如果在 JavaScript 内定义了 URL 内容,那么您需要在 WebSEAL 中启用脚本过滤。请考虑在清单 3 和 4 中所展示的场景。

清单 3. widget.js
<script>        
        function getXML () {
        
            var url_1 = "http://sales.acme.org/sales.xml";
            var url_2 = "http://" + server + ":" + port + "/sales.xml";
            var url_3 = "/sales.xml";
            var url_4 = "sales.xml";
            
            var d = dojo.xhrGet ({
                                  url:      url_1,
                                  handleAs: 'text', 
                                  load:     loadIntoNode,
                                  error:    errorCondition
                                 });
        };
        function loadIntoNode(data, ioArgs){
        
            // process XML file
        }
        ...
        
</script>
清单 4. widget.html
<body class="tundra">
...

<button id="1469" dojoType="dijit.form.Button" onClick="getXML();")'>
XML Data Button
</button>

...
</body>

在上述例子中,发生了如下问题:

  • 这些 URL 不会被重写,除非脚本过滤已经被正确配置。
  • 如果 http://sales.acme.org/sales.xml 链接没有被重写,浏览器就会提出一个异常,因这违背了同源策略。
  • 如果启用了脚本过滤,在 example.js 内定义的变量 url_1 就会被 WebSEAL 重写。
  • 即便正确地定义了脚本过滤,WebSEAL 仍不能解释 url_2 内使用的动态链接结构的等级。
  • 虽然 WebSEAL 具备过滤 JavaScript 的能力,但通常还是很难区分一个变量是实际字符串文本还是相对的或服务器-相对的 URL。绝对的 URL 可被正确重写,由于相对 URL 无需更改,因此会带来问题的 URL 类型只能是服务器-相对 URL。这是因为 WebSEAL 必须要将联结名添加到服务器-相对 URL 的路径来指代位于所联结的服务器上的资源。如果不能判断像 url_3 那样声明的变量是否是一个服务器-相对 URL,那么所有以这种方式发出的请求都将导致死链。

可能的解决方案

WebSEAL 足够智能,足以过滤 JavaScript,但您需要告诉它这么做。如果只是打开了 JavaScript 过滤,将不能获得所想要的效果。启用了 JavaScript 过滤的一种标准 WebSEAL 配置将会只查找内容上类似下表内所示的那些 URL。

未过滤的内容过滤了的内容
<a href="javascript:doRequest('http://sales.acme.org/somecontent.html')">click here</a><a href="javascript:doRequest('http://acme.org/sales_junciton/somecontent.html')">click here</a>
<script type="text/javascript" src="//sales.acme.org/js/script.js"></script><<script type="text/javascript" src="//acme.org/sales_junction/js/script.js"></script>

要获得在一个外部 JavaScript 内所定义的内容,需要使用类似清单 5 所示的配置。

清单 5. webseald.conf 内所需的更改
[filter-content-types]
type = text/html
type = text/xml
type = text/javascript

[script-filtering]
script-filter = yes

如何适合您现有的架构,另一种可能的方法是利用虚拟主机联结。通过这么做,可以有效消除过滤 HTML 内容内的 URL 的需要。当需要在您的客户机端脚本内重写这些 URL 时,问题将少得多。不过,虚拟主机联结并不适合企业内 WebSEAL 的所有部署,所以关键要选择适合您具体环境的最佳方案。

结束语

本文展示了如何联合使用 IBM Tivoli Access Manager WebSEAL 与 IBM WebSphere Application Server Feature Pack for Web 2.0。WebSEAL 可被用来提供一个安全代理环境,而 Web 2.0 功能部件包则可提供服务器和客户机应用程序技术来创建 Ajax 风格的架构。通过提供示例场景,本文探索了可能发生的各种问题以及潜在的解决方案。


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=WebSphere, Web development, Tivoli
ArticleID=449673
ArticleTitle=网络中的 Ajax: 在 Ajax 架构中聚合来自多个站点的内容所面临的安全性和拓扑问题
publish-date=11252009