利用 AuthenticRoast 自定义托管容器的安全性

用于简化 JEE 安全性模块开发的一个开放源码项目

AuthenticRoast 是一个开放源码项目,它能够与 Java™ Authentication Service Provider Interface for Containers (JSR 196) 协同工作,从而允许您开发自定义身份验证模块,以便配合托管容器的声明式安全性一起使用。Joe Sam Shirah 将展示 AuthenticRoast 如何最大程度地降低对 Java Enterprise Edition (JEE) 容器的配置影响,同时极大地减少自定义安全性要求的编码工作。文中提供了一个可供下载的 WAR,附带演示代码。

Joe Sam Shirah, 负责人和开发人员, conceptGO

http://www.ibm.com/developerworks/i/p-jshirah.jpgJoe Sam Shirah 是 conceptGO 的负责人和开发人员,该公司提供远程咨询和软件开发服务以及产品,专长是 JDBC、I18N、AS/400、RPG、金融、库存以及后勤方面。Joe Sam 于 1998 年 JavaOne 上获得 Java 社区奖,他也是 Java Developer Connection 上的 JDBC 2.0 Fundamentals 短期课程的作者。他是 developerWorks“Java filter”论坛的主持人,还是 jGuru 的 JDBC、国际化和 Java400 FAQ 的管理人员。Joe Sam 拥有经济学工商管理学士学位以及国际管理硕士学位。



2012 年 2 月 27 日

不久之前,我的两位客户要求为其 Web 应用程序提供自定义的安全性。在一个新项目中,客户主要以 IBM i 为主,这家客户希望用户通过 IBM i 登录访问应用程序。另外一家客户则希望增强现有应用程序,使某些用户能够通过一个安全数据库进行验证,而 “标准” 用户则通过现有轻量级目录访问协议 (LDAP) 条目进行验证。

两者均为企业级应用程序(而非面向公共的 Web 页面),这些应用程序在本地运行或者通过虚拟专用网运行。总体而言,托管容器的安全性在这些环境下均表现良好,使应用程序免于承担过滤器或其他用于检查每一页中的凭据的代码。新要求的问题在于,托管容器的安全像通常由安全域 实现,这些安全域包括 Apache Tomcat、IBM WebSphere 或 GlassFish 等容器提供的用户数据源机制。安全域的实现各有不同,特定于给定的容器。这些应用程序可能运行在 GlassFish 之中,并未给访问 IBM i 登录定义任何领域,也不支持单独一个领域内的多种身份验证和授权方法。

我的目标是在最初能够按照自定义的方式对用户进行身份验证和授权,随后将安全性检查任务转交给托管容器的安全性。在这篇文章中,我将展示我从 Java Authentication and Authorization Service (JAAS) 和 JSR 196 到使用 AuthenticRoast 的解决方案的过程,这是一个根据 GNU Lesser General Public License 获得许可的项目。文中包含一个演示 WAR,可在 GlassFish 下运行(请参见 下载)。首先,我将简要介绍托管容器的安全性,重点关注基于表单的身份验证。

托管容器的安全性的概念

托管容器的安全性 是 Java Servlet 规范(请参见 参考资料 部分)中列出的声明性安全功能的通用说法。如果托管容器的安全功能符合某个应用程序的要求,则可使程序员免除大量繁冗、易出错的编码工作。此外它们还能为多个应用程序之间的身份验证和授权提供一致性。

角色

角色 是指定的用户、经理、管理员等人员的分组。用户可以是多个分组的成员。与尝试分别控制用户相比,角色和分组极大地简化了安全性管理与编程。

托管容器的安全性背后的概念在于:用户和角色是在应用程序之外定义的,同时提供了获取凭据的标准方法。应加以保护的 Web 页面同样是在外部定义的,仅可由经过授权的用户或角色访问。托管容器的安全性可自动控制访问,使用映射到页面或页面分组的声明式角色来实现这样的控制。

Servlet 规范定义了四种类型的身份验证:

  • HTTP 基本身份验证
  • HTTP 摘要式(Digest)身份验证
  • HTTPS 客户端身份验证
  • 基于表单的身份验证

规范还强烈建议应用服务器实现 Java Authentication SPI for Containers 的 Servlet 容器配置文件,如 JSR 196 所述(请参见 参考资料 部分)。

在用户经过身份验证之后,您就可以使用以下 HttpServletRequest 方法,对特定页面元素进行更细粒度的控制:

  • getRemoteUser()
  • getUserPrincipal()
  • isUserInRole(java.lang.String role)

配置

标准托管容器的安全性需要安全性约束、安全性角色、登录和可选的角色映射的配置。登录配置定义和描述上述四种类型的身份验证之中的一种类型。如后文所述,AuthenticRoast 无需登录配置,不依赖于作为 Authenticator 使用的类来调用恰当的身份验证类型。

尽管最新版本的 Servlet 规范引入了注释和其他编程方法来定义安全性约束,但我在本文的 web.xml 文件中使用了声明,因为声明更为人所熟悉、处于一个位置,并且可供应用程序部署人员和管理人员访问。有关本文中未用到的其他元素、子元素和细节,请参见 参考资料 部分。

安全性约束

web.xml 文件的 <security-constraint> 元素声明了要保护的页面,以及允许访问这些页面的角色。清单 1 展示了一个示例:

清单 1. <security-constraint> 元素
<security-constraint>
    <web-resource-collection>
        <web-resource-name>Protected Pages</web-resource-name>
        <url-pattern>/protected/*</url-pattern>
    </web-resource-collection>
    <auth-constraint>
        <role-name>boss</role-name> 
        <role-name>mgr</role-name> 
        <role-name>user</role-name> 
    </auth-constraint>
</security-constraint>

<web-resource-collection> 子元素负责为集合命名,并定义要保护的一个或多个 <url-pattern> 元素。在 清单 1 所示的例子中,该集合称为 Protected Pages。受保护的目录中的所有页面均服从 <auth-constraint> 子元素,该子元素指定了经过授权的角色名称。在演示代码中,这些角色包括 bossmgruser。请注意所有 HTTP 方法类型( GET, POST 等),在默认情况下,它们同样服从约束;如有必要,您可以指定包含或忽略特定类型。

安全性角色

清单 2 中的 <security-role> 元素提供了 清单 1<auth-constraint> 子元素中列出的角色的角色定义:

清单 2. <security-role> 元素
<security-role> 
    <description>boss</description>
    <role-name>boss</role-name>
</security-role> 
<security-role> 
    <description>mgr</description>
    <role-name>mgr</role-name>
</security-role> 
<security-role> 
    <description>user</description>
    <role-name>user</role-name>
</security-role>

基于表单的身份验证

基于表单的身份验证允许您为托管容器的安全性自定义 UI。如清单 3 所示,<form /> 元素必须使用 POST 方法和 action 选项的内容为 j_security_check。表单还必须包含名为 j_usernamej_password<input /> 元素,使用它们提供身份验证所用的用户 ID 和密码。

清单 3. 基于表单的身份验证 Web 页面的最低要求
<form method="POST" action="j_security_check"> 
<input type="text" name="j_username"> 
<input type="password" name="j_password"> 
</form>

在您的 Web 页面满足了上述最低要求之后,您就可以添加任何必要内容,以匹配您的应用程序标准。举例来说,图 1 展示了演示代码的登录页面,其中包含一个标头部分,带有图像、标签和说明。

图 1. dwAuthenticRoastDemo 登录页面
AuthenticRoast 演示登录页面的屏幕快照

登出(logout)通常是通过使会话失效实现的。您还可以使用 HttpServletRequest logout() 方法,取代或补充用以重置调用方身份的失效。

标准托管容器的安全性还需要在 web.xml 中使用一个 <login-config /> 条目,如清单 4 所示:

清单 4. 示例 <login-config /> web.xml 条目
<login-config>
    <auth-method>FORM</auth-method>
    <realm-name>SomeRealmName</realm-name>
    <form-login-config>
        <form-login-page>/login.jsp</form-login-page>
        <form-error-page>/login-error.jsp</form-error-page>
    </form-login-config>
</login-config>

<login-config /> 条目定义身份验证方法和安全域。由于本示例使用了基于表单的方法,因此必须使用一个 <form-login-config /> 元素,定义登录和错误所用的页面。

如果应用服务器支持的安全域类型能够满足您的需求,那么这种配置就能完美地发挥作用。否则,对于指定安全域的依赖关系,标准托管容器的安全性会带来一些问题。也正是这样的难题促使我着手开始寻找一种合适的解决方案。


JAAS 的问题

JAAS 是一种低级 API,集成于 Java SE 1.4 版本之中,用于开发身份验证和授权规则(请参见 参考资料 部分)。在处理我的项目时,我最先想到的解决方案就是 JAAS;它极为灵活、支持可插入的模块,并且是 Java SE 的标准部分。

遗憾的是,JAAS 的规范是在 JEE 安全性机制出现之前制定的。JAAS 对于独立应用程序表现良好,但不具备与 servlet 或 JEE 应用程序的标准集成或绑定。它还使用 javax.security.auth.Subject 类作为授权角色的基础,此类包含多个准则,而托管容器的安全性则使用 java.security.Principal 类。

由于不具备集成标准,因此 servlet 或 JEE 容器厂商对于 JAAS 的任何支持均为专有支持。从某种意义上来讲,安全域拥有相同的问题,但我决定,如果没有更出色、更标准的解决方案可用,我将诉诸于专有 JAAS 或者我自己的 JAAS 实现。


JSR 196 解决危机?

Java Authentication Service Provider Interface for Containers 有时也称为 JASPI 或 JASPIC,但更多人称之为 JSR 196。其目标在于指定一种标准,允许配置自定义身份验证机制,利用一个兼容的容器来实现声明式安全性。JSR 为各种上下文定义了配置文件,包括 servlet 在内。

早在 2004 年,我就听说了 JSR 196,但它受困于 Java Community Process,直到 2007 年才通过最终草案。与此同时,GlassFish 作为 JEE 的参考实现,从 2.1 版本开始便为 JSR 的 Servlet 容器配置文件部分提供了支持。JSR 196 包含于 JEE 6 规范之中(请参见 参考资料 部分)。

除了常规安全性术语和明确相关的服务器身份验证模块 (SAM) 的概念之外,JSR 196 还使用了许多有时令人费解的术语,例如消息身份验证模块消息处理运行时面向容器的 Java 授权协议(JACC 或 Java ACC)。尽管如此,我仍然坚持使用这种方法,直到遇到了两个严重的问题:

  • 每个 SAM 都必须配置并有效集成到容器之中,而不能仅仅处于一个应用程序之中或者为一个应用程序进行配置。
  • 按照示例中给出的所有步骤操作,例如 JSR 196 技术指南给出的企业技术提示 “为 GlassFish Servlet 容器添加身份验证机制”,请参见 参考资料 部分,都需要 庞大的工作量。

尽管我希望使用 JSR 196 方法,但如果有可能,我会寻找某种方法来最大程度地减少实现 SAM 所需的影响和工作量。又经过一段时间的调研之后,我发现了一个开放源码项目,其目的与我的想法不谋而合。


AuthenticRoast

在本文撰写之时,AuthenticRoast 项目(请参见 参考资料 部分)的最新版本是 0.3.3 版本,但是,早在三年之前,这个项目便已在生产中得到应用。我的两个项目使用 AuthenticRoast 已分别有两年和一年的时间了。

AuthenticRoast 包含三个主要 JAR,将工作分为两个部分:

AuthenticRoast 适用于哪些容器?

AuthenticRoast 在 GlassFish 2.x/3.x 和 Tomcat 6 上进行了测试。(Tomcat 版本是一个 valve 组件,使用内部调用,这是因为 Tomcat 不包含对 JSR 196 的原生支持。) “GlassFish” 或 JSR 196 版本可使用任何 JEE 6 兼容的容器,例如 WebSphere 8。然而,我发现在互联网上没有任何有关使用 GlassFish 以外的服务器的报告。

要将 AuthenticRoast 与另外一种容器配合使用,您至少需要转换 GlassFish 的消息安全性配置中的基本模块注册。您还必须将 glassfish-web.xml 中的 httpservlet-security-provider 转换为您的容器的对应部分。对于 WebSphere,一个应用程序的提供者关联 (provider association) 是使用管理控制台中的 Map JASPI Provider 选项实现的(请参见 参考资料 部分)。

如果您使用另外一种容器集成 AuthenticRoast,请考虑将流程提交给 AuthenticRoast 项目主管,以协助指导其他开发人员。

  • AuthenticRoast-API-ver.jar 和 AuthenticRoast-Impl-ver.jar:容器集成不可避免,但这些 JAR:
    • 允许一次性容器配置。
    • 作为容器及您的应用程序的安全性模块之间的纽带(验证者)。
  • AuthenticRoast-Extras-ver.jar:尽管其名称可能会产生歧义,但您可以在大多数应用程序中使用这个 JAR(除非您热爱编写自定义代码),因为其中包含以下抽象类,适合可与托管容器的安全性一起使用的各种类型的身份验证:
    • BasicAuthenticator
    • CompositeAuthenticator
    • FormAuthenticator
    • SSLClientAuthenticator
    • TicketAuthenticator

在本文后续的部分和演示代码中,我将展示如何将 FormAuthenticator 类与 GlassFish 3.1.1 配合使用。

AuthenticRoast 身份验证开发过程涉及四个领域:

  • 容器配置
  • 应用程序配置
  • 验证者注册
  • 验证者代码

我会对它们依次加以说明。


容器配置

令人满意的是,在使用 AuthenticRoast 时,容器的配置(本例中的容器是 GlassFish)是一项一次性的任务。项目 wiki 的 InstallationForGlassfish 页面(请参见 参考资料 部分)非常简单,而且,由于未来可能会出现变化,因此我会指导您到那里去了解配置步骤。这些说明面向 GlassFish 2.x,而图 2、图 3、图 4、图 5 指出了 GlassFish 3.1.1 的不同之处。在开始之前,务必确保已将 AuthenticRoast-API-ver.jar 和 AuthenticRoast-Impl-ver.jar 复制到了 GlassFish 安装的 lib 文件夹中,并重新启动服务器。

如图 2 所示,在单击 Message Security 时,您可以看到 HttpServlet 身份验证层已经存在。单击 HttpServlet

图 2. GlassFish 3.1.1 Message Security Configurations 页面
GlassFish 3.1.1 Message Security Configurations 的屏幕快照

此时将显示如图 3 所示的 Edit Message Security Configuration 页面:

图 3. GlassFish 3.1.1 Edit Message Security Configuration 页面
GlassFish 3.1.1 Edit Message Security Configuration 页面

单击 Providers 选项卡。

图 4 展示了 HttpServlet 身份验证层已经存在的原因:GlassFish 管理控制台本身现在使用一个安全性提供者。图 4 还表示,我已经创建了 roast 提供者。单击 New 按钮即可添加此提供者。

图 4. GlassFish 3.1.1 Provider Configurations 页面
GlassFish 3.1.1 Provider Configurations 的屏幕快照

图 5 展示了 roast 提供者的条目。这些条目与 AuthenticRoast InstallationForGlassfish wiki 页面中列出的条目相同。将页面下方的 Response Policy 字段(图 5 中未显示)留空,不要选择任何选项。

图 5. GlassFish 3.1.1 Edit Provider Configuration 页面
GlassFish 3.1.1 Edit Provider Configuration 页面的屏幕快照

务必保存配置并重新启动服务器。完成此过程之后,即可在您的应用程序中使用 AuthenticRoast。


应用程序配置

一个使用 AuthenticRoast 的应用程序的 web.xml 文件遵循托管容器安全性的标准,如 清单 1清单 2 所示。然而,请务必注意一个例外情况:AuthenticRoast 不需要且不使用 清单 4 中的 <form-login-config /> 元素

在下一节中您可以看到,AuthenticRoast 要求您的验证者注册容器,这项任务通常是由一个应用程序侦听器执行的。清单 5 展示了定义演示应用程序侦听器的条目:

清单 5. web.xml 中的应用程序侦听器定义条目
<listener>
  <listener-class>com.dw.ARDAppInit</listener-class>
</listener>

在上一段必要的配置中,应用程序通知容器,它使用一个 httpservlet 安全性提供者。特定于容器的 glassfish-web.xml 文件(在 GlassFish 3.x 之前的版本中称为 sun-web.xml)中,<glassfish-web-app /> 元素的 httpservlet-security-provider 属性利用一个匹配指定提供者 ID 的条目 (roast) 完成了此任务:

<glassfish-web-app httpservlet-security-provider="roast" error-url="">

角色映射是一项可选但有用的 JEE 特性。它允许部署人员将不同的分组或角色映射到应用程序中使用的分组或角色。遗憾的是,条目和位置是特定于容器的;举例来说,WebSphere 使用 ibm-application-bnd.xml,而 GlassFish 又使用 glassfish-web.xml。清单 6 展示了将应用程序中使用的 <role-name /> 元素映射到特定于部署站点的 <group-name /> 的条目:

清单 6. glassfish-web.xml 中的角色映射
<security-role-mapping>
    <role-name>boss</role-name>
    <group-name>ARDBoss</group-name>
</security-role-mapping>
<security-role-mapping>
    <role-name>mgr</role-name>
    <group-name>ARDMgr</group-name>
</security-role-mapping>
<security-role-mapping>
    <role-name>user</role-name>
    <group-name>ARDUser</group-name>
</security-role-mapping>

由于使用了这些条目,举例来说,在演示应用程序检查 boss 时,容器能确保 ARDBoss 角色符合条件。


验证者注册

ARDAppInit 是演示示例的应用程序侦听器,是在 清单 5 中定义的。如清单 7 中所示,它使用 contextInitialized() 方法,在应用程序启动时注册 authenticator 类的一个实例:

清单 7. 演示代码验证者注册
package com.dw;

import javax.servlet.ServletContext;
import javax.servlet.ServletContextEvent;
import javax.servlet.ServletContextListener;
import name.aikesommer.authenticator.Registry;
...
public class ARDAppInit implements ServletContextListener
{
  ...
  public void contextInitialized( ServletContextEvent sce )
  {
    ServletContext sc = null;

    sc = sce.getServletContext();
    // register AuthenticRoast authenticator
    Registry.forContext( sc ).register( new ARDFormAuthenticator() );

    System.out.println( "[ARDAppInit Executed]" );

  } // end contextInitialized

} // end class ARDAppInit

此代码在所有应用程序中实际上都是相同的;通常情况下,惟一的变化就是包的名称和您的验证者实例的设置。


验证者代码

前面的讨论、配置和设置为基础,使用您自己的自定义代码启用基于表单的身份验证应该会令您惊喜,因为这通常仅需以下步骤:

  • 扩展 FormAuthenticator
  • 覆盖至多四个(通常是两个)FormAuthenticator 的方法
  • 对于 SimplePrincipal 类略知一二

FormAuthenticator 类(位于 AuthenticRoast-Extras-ver.jar 中)扩展了 PluggableAuthenticator,后者与 SimplePrincipal 同样位于 AuthenticRoast-API-ver.jar 之中。容器通过 AuthenticRoast 对类实例做出方法调用。

如果您愿意为登录页面使用名称 login.jsp、为登录错误页面使用名称 login-error.jsp,则需要处理两个 FormAuthenticator 方法。若非如此,可重写 String getErrorPage()String getLoginPage(),以返回您的这些页面的首选名称。

始终需要重写的两个方法如下:

  • boolean checkCredentials(AuthenticationManager manager, AuthenticationRequest request, String username, String password)

    checkCredentials() 方法负责验证传入的凭据,即用户名和密码。如果凭据通过验证,则返回 true,否则返回 false

  • SimplePrincipal loadPrincipal(AuthenticationManager manager, AuthenticationRequest request, String username)

    仅有 checkCredentials() 返回了 true,表示用户通过验证的情况下,才应调用 loadPrincipal() 方法。该方法将返回一个 SimplePrincipal,实际上就是一个用户名和一个分组名 Set。构造函数获取一个用户名和一个包含与该用户关联的分组或角色名的 String 数组。容器搜索组名,寻找与 web.xml <auth-constraint> 元素中列出的角色匹配的分组名(请参见 清单 1),或者与之映射的分组名(请参见 清单 6)。清单 8 给出了一个加载 SimplePrincipal 的示例:

    清单 8. 使用静态数组加载 SimplePrincipal
    String[] asBossGroups = 
              { "ABCBoss", "ARDBoss", "MVBoss", "RSBoss" }; 
    
    SimplePrincipal sp = new SimplePrincipal( "bossLady", asBossGroups );

请注意,这两个方法原本是无状态、不相关的,因此各方法中的用户和分组数据访问往往会出现重复。当然,您可以使用额外的代码来改变这种状况,但需要密切注意这方面的问题。

验证者代码框架就是这样,与常见的使用两种方法相比,项目得到了极大的简化,我想您也会认同这个观点。


关于演示应用程序

本文相关的演示应用程序(请参见 下载 部分)提供了使用 AuthenticRoast 的具体示例。除了用于登录验证的页面之外,示例仅使用受保护目录中的一个页面,仅显示调用 getRemoteUser()getUserPrincipal()isUserInRole(java.lang.String role) 的结果,如图 6 所示:

图 6. 登录结果页面
AuthenticRoast 演示登录结果页面图像的屏幕快照

checkCredentials() 方法接受了三个用户/密码集:

  • 用户 stevie,密码 user1
  • 用户 ray,密码 mgr1
  • 用户 vaughan,密码 boss1

如果凭据通过验证,SimplePrincipal 对象中的 loadPrincipal() 方法将返回用户名和相关分组名数组:

  • asUserGroups 针对用户 stevie
  • asMgrGroups 针对用户 ray
  • asBossGroups 针对用户 vaughan

清单 9 展示了数组内容:

清单 9. ARDFormAuthenticator 使用的分组数组
  private static String[] asBossGroups = 
          { "ABCBoss", "ARDBoss", "MVBoss", "RSBoss" }; 
  private static String[] asMgrGroups = 
          { "ABCMgr", "ARDMgr", "MVMgr", "RSMgr" }; 
  private static String[] asUserGroups = 
          { "ABCUser", "ARDUser", "MVUser", "RSUser" };

几乎其他全部代码和配置(包括角色映射在内)均显示在清单 1 至清单 7 中。如上文所述,惟一的例外就是 清单 4 中所示的 <login-config /> 元素,它在 AuthenticRoast 中是不必要且无用的。


结束语

只有在少数情况下,托管容器的安全性才需要自定义模块,但在此类情况下,这样的需求可能极为关键。JSR 196 实现了为自定义声明式安全性提供集成的目标,但代价是有时会变得极为繁琐和复杂。AuthenticRoast 项目简化了这个过程。

务必牢记 AuthenticRoast 的主要优点,即您可以完全掌控一切,但这也是它最大的缺陷:您必须面面俱到。举例来说,在之前使用 GlassFish LDAP 领域访问 Microsoft Active Directory 以获取密码和分组数据时,我只需设法搜索字符串一次即可。如果 LDAP 凭据出现故障,那么在使用 AuthenticRoast 添加额外的验证流程时,我必须深入研究并自行编写 LDAP 访问代码。

然而,这样的缺陷在直接使用 JSR 196 或其他任何方法来创建自定义安全性模块时同样存在。最终,AuthenticRoast 是一种开放源码解决方案,在最大限度地减少对于 JEE 容器的配置影响和显著减少自定义安全性需求所需的编码工作量方面,该解决方案取得了极大的成功。


下载

描述名字大小
本文的演示 WAR1j-authenticroast.zip86KB

注意:

  1. WAR 文件中包含 Java 源代码。有关使用 WAR 文件的说明,请参见下载部分中包含的 readme.txt 文件。

参考资料

学习

获得产品和技术

讨论

  • 加入 developerWorks 中文社区。查看开发人员推动的博客、论坛、组和维基,并与其他 developerWorks 用户交流。

条评论

developerWorks: 登录

标有星(*)号的字段是必填字段。


需要一个 IBM ID?
忘记 IBM ID?


忘记密码?
更改您的密码

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件

 


在您首次登录 developerWorks 时,会为您创建一份个人概要。您的个人概要中的信息(您的姓名、国家/地区,以及公司名称)是公开显示的,而且会随着您发布的任何内容一起显示,除非您选择隐藏您的公司名称。您可以随时更新您的 IBM 帐户。

所有提交的信息确保安全。

选择您的昵称



当您初次登录到 developerWorks 时,将会为您创建一份概要信息,您需要指定一个昵称。您的昵称将和您在 developerWorks 发布的内容显示在一起。

昵称长度在 3 至 31 个字符之间。 您的昵称在 developerWorks 社区中必须是唯一的,并且出于隐私保护的原因,不能是您的电子邮件地址。

标有星(*)号的字段是必填字段。

(昵称长度在 3 至 31 个字符之间)

单击提交则表示您同意developerWorks 的条款和条件。 查看条款和条件.

 


所有提交的信息确保安全。


static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Java technology, Open source
ArticleID=795883
ArticleTitle=利用 AuthenticRoast 自定义托管容器的安全性
publish-date=02272012