#
2007 年 10 月 18 日
本文是 Acegi Security Systerm 介绍的最后一部分(共三部分),Bilal Siddiqui 将向您介绍如何保护对 Java 类实例的访问,从而结束本系列文章。通过本文了解为何需要对 Java™ 类的访问进行保护,Spring 如何创建和保护对 Java 类实例的访问以及如何对 Acegi 进行配置以实现 Java 应用程序的类安全性。
这期共分三部分的系列文章介绍了如何使用 Acegi 安全系统保护 Java 企业应用程序。系列文章的 第 1 部分 简单介绍了 Acegi 并解释如何使用其内置的安全过滤器实现一个简单的、基于 URL 的安全系统。第 2 部分 介绍了如何编写访问控制策略并将其保存到一个 LDAP 目录服务器,以及如何配置 Acegi 来与目录服务器进行交互,从而实现访问控制策略。第 3 部分(也是本系列的最后一篇文章)将演示如何在企业应用程序中使用 Acegi 保护对 Java 类实例的访问。
首先我将介绍何时需要对 Java 类访问进行保护,包括文中引用的两个典型企业应用程序场景。之后,我将解释 Spring 的反转控制(IOC)框架如何创建可从 JSP 或 servlet 访问的 Java 类实例。我还将介绍有关 bean 代理 的重要概念,Spring 正是使用它过滤对 Java 类的访问。最后,我将介绍如何对 Acegi 的方法安全性拦截器进行配置以控制对 Java 类的访问。我将对 第 2 部分 中的示例程序进行增强,为实现安全的 Java 对象提供支持,从而结束本系列的最后一篇文章。
由于本文的讨论构建在本系列前两部分的内容之上,因此会经常引用到 第 1 部分 和 第 2 部分 中的讨论和示例。因此,在继续阅读本文之前,在其他浏览器窗口中打开前两期文章将有助于理解本文内容。
保护 Java 类的用例
您可能还记得,我曾在本系列的开头部分简单介绍了 企业应用程序安全性。在那次讨论中我曾提到过一种场景,其中 URL 安全性并不能完全满足这种场景的安全需求:
假设有这样一个 PDF 文档,其中包含了某制造业公司生产的特定产品的数据。文档的一部分包含了设计数据,将由公司设计部分进行编辑和更新。文档另一部分包含生产经理将使用到的生产数据。对于此类场景,需要实现更加细粒度的安全性,对文档的不同部分应用不同的访问权限。
在继续阅读之前,请考虑更多的应用程序场景,除了实现 URL 安全性以外,这些场景还要求您对单独的类访问进行保护。
业务自动化
业务自动化应用程序中的工作流由多个流程组成。例如,病理学实验室中执行血液测试的工作流由若干个步骤组成,其中每个步骤可看作一个业务流程:
- 工作人员从病人处采集血液样本并为其分配一个 ID。
- 实验室技术人员对样本进行必要的测试并准备测试结果。
- 由具备相应资格的病理学专家根据测试结果编写测试报告。
很明显,每个流程分别由单独的授权用户执行。未授权的用户则无权执行流程。例如,实验室研究人员只负责准备试验结果,而无权编写测试报告。
几乎所有的业务自动化应用程序都普遍使用授权的业务流程。通常,每个业务流程被实现为一个 Java 类,并且需要使用合适的访问控制策略对所有类实施保护。
企业对企业(Business-to-business)集成
Business-to-business (B2B) 集成指一种常见的场景,其中的两个企业实体需要彼此公开各自的特定功能。例如,宾馆可能向旅游公司公开其房间预订功能,而后者使用该功能为游客预订空闲的房间。作为合作伙伴的旅游公司可能具有一个特定的订房率。在这个场景中,宾馆的订房系统必须先对旅游公司进行身份验证,然后才能允许他们访问所选择的类,以便按照特定的订房率进行房间预订。
使用 Spring 创建 Java 对象
现在您已经了解了对 Java 类示例的访问进行保护的重要性。在介绍能够实现更高级安全性的 Acegi 新功能之前,我将引导您回顾 Spring 框架的几个关键特性,您需要了解这些内容才能继续后文的示例。
首先对一些 Java 类进行配置并执行实例化。第 1 部分 曾介绍过,Java 类在 Spring 的 XML 配置文件中进行配置。在 Spring 配置文件中配置 Java 类的过程与 Acegi 过滤器的配置过程完全相同,因此这里不多做介绍。相反,我们将查看清单 1,它展示了名为 publicCatalog
的 bean 的配置:
清单 1. Acegi XML 配置文件
<beans>
<bean id="publicCatalog"
class="com.catalog.PublicCatalog" />
<!--Other bean tags -->
<beans>
|
了解 Spring 的 IOC 框架如何从 XML 配置文件读取 Java 类信息以及如何进行实例化,这一点非常重要。您可能还记得,我在系列文章的 第 1 部分 中使用一个 web.xml 文件配置 <listener>
标记,它指向名为 ContextLoaderListener
的类。ContextLoaderListener
装载 Spring 的 IOC 框架并创建 Java 对象。您可以参考 第 1 部分的清单 8 查看全部内容。图 1 也对此进行了描述:
图 1. 装载 Spring 的 IOC 框架并创建 Java 对象
现在我们将详细讨论这些步骤:
- 当初始化 Acegi 应用程序时,servlet 容器(本例中为 Apache Tomcat)创建了一个 servlet 上下文,其中保存了有关应用程序资源的信息,例如 JSP 页面和类。
- servlet 容器通知
ContextLoaderListener
类应用程序正在启动。
ContextLoaderListener
类创建一个 Web 应用程序上下文以保存应用程序中特定于 Spring 的资源信息。借助 Spring 的 IOC 框架,您可以装载自己的自定义应用程序上下文。要创建应用程序上下文,将使用名为 ContextLoader
的上下文装载器类装载应用程序上下文。
- 如果应用程序不需要定义自己的应用程序上下文,则可以使用名为
XMLWebApplicationContext
的类,它是 Spring 框架的一部分并提供可处理 Spring XML 配置文件的功能。Acegi 应用程序使用的是 Spring 的 XML 配置文件,因此本文仅讨论由 XMLWebApplicationContext
类表示的应用程序上下文。在本例中,上下文装载器对 XMLWebApplicationContext
类进行实例化,后者表示您的 Acegi 应用程序的应用程序上下文。上下文装载器还在 Web 应用程序上下文中设置 servlet 上下文(于步骤 1 中创建)的引用。
XMLWebApplicationContext
类对 XML 配置文件进行解析,获得关于 Java 类的信息并将信息装载到其他内部对象中。
XMLWebApplicationContext
类对 XML 配置文件中指定的所有 Java 类进行实例化。XMLWebApplicationContext
类检查 XML 配置文件中经过配置的 Java bean 是否依赖其他的 Java 对象。如果是的话,XMLWebApplicationContext
类将首先对其他 bean 所依赖的 bean 进行实例化。通过这种方式,XMLWebApplicationContext
类创建了 XML 配置文件中定义的所有 bean 的实例。(注意,步骤 6 假定 XML 配置文件中所有 bean 都不要进行保护,稍后一节将介绍步骤 5 和步骤 6 之间执行的额外步骤,从而保护对此处创建的 Java bean 的访问)。
XMLWebApplicationContext
类将所有 bean 保存在一个数组中。
您现在已了解到如何从 XML 配置文件中装载 bean 定义并创建 Java 类的实例。接下来,我将向您介绍 Spring bean 代理并解释它对于保护 Java 类实例的重要性。
使用 bean 代理
上一节讨论了 Spring 的 IOC 框架对 Java 对象进行实例化。要保护对 Java 对象的访问,Spring 的 IOC 框架使用了 bean 代理 的概念。本节首先介绍如何配置 bean 代理,然后演示 Spring 的 IOC 框架如何创建代理对象。
为 Java 对象配置代理
如果希望创建 bean 代理,Spring IOC 框架要求您对代理创建器 bean 的实例进行配置。Spring 的 IOC 框架使用代理创建器创建代理对象。清单 2 为代理创建器 bean 的配置文件,用于保护名为 privateCatalog
的 Java 对象:
清单 2. 代理 bean 配置
<bean id="proxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>privateCatalog</value>
<!--Names of other beans to be proxied -->
</list>
</property>
<property name="interceptorNames">
<list>
<value>privateCatalogSecurityInterceptor</value>
</list>
</property>
</bean>
|
如清单 2 所示,<bean>
标记具有一个 class
属性,其值为 org.springframework.aop.framework.autoproxy. BeanNameAutoProxyCreator
。BeanNameAutoProxyCreator
类是 Spring IOC 框架的一部分,可以自动创建 bean 代理。Spring 框架提供了 BeanPostProcessor
接口,它提供了一种可扩展机制,允许应用程序编写自己的逻辑来创建 bean 代理。Spring 的 BeanNameAutoProxyCreator
类实现了 BeanPostProcessor
接口并提供所有必需的代理创建逻辑来保护 Java 类。因此,本文中您无需实现 BeanPostProcessor
接口。
在创建 bean 代理时,BeanNameAutoProxyCreator
类为所有由 beanNames
属性定义的 bean 创建代理(参见 清单 2 中 <bean>
标记的第一个 <property>
子元素)。beanNames
属性在 <list>
标记中包含一个 bean 名称列表。在 清单 2 中,我只对希望为之创建代理的 privateCatalog
bean进行了配置。
现在查看 清单 2 中 <bean>
标记的第二个 <property>
子元素。它指定了名为 interceptorNames
的代理,它将一个或多个拦截器的名称封装起来。我将在后文详细讨论拦截器概念。现在,只需了解拦截器可以拦截用户并在用户访问 bean 之前实现访问控制策略。
现在,您已了解了如何对希望进行保护的 bean 配置代理。接下来,您将了解 Spring 的 IOC 框架如何在内部为应用程序的 bean 创建代理对象。
Spring IOC 发挥效用
在 “使用 Spring 创建 Java 对象” 的步骤 5 和步骤 6 中,您了解了 XMLWebApplicationContext
类如何从 XML 配置文件中读取 bean 定义并随后创建 bean 实例。在创建 bean 实例之前,XMLWebApplicationContext
类将检查 XML 配置文件是否包含任何代理创建器 bean(即实现 BeanPostProcessor
接口的 bean)配置。如果存在该 bean,它将要求代理创建器为您希望进行保护的 bean 创建 bean 代理。
现在考虑代理创建器如何在内部创建代理对象:
- 代理创建器(即
BeanNameAutoProxyCreator
类)装载 清单 2 中配置的 beanNames
属性文件中指定的所有 bean 名称。
- 代理创建器使用 bean 名称装载各自的 Java 类,这些类使用了每个 bean 定义的
class
属性。
- 代理创建器创建 清单 2 所示的
interceptorNames
属性中指定的拦截器的实例。
- 最后,代理创建器创建一个
Cglib2AopProxy
类的实例,将所有 bean 名称(步骤 2)和拦截器(步骤 3)传递到 Cglib2AopProxy
类。Cglib2AopProxy
类是 Spring 框架的一部分并用于生成动态代理对象。在本例中,Cglib2AopProxy
类将创建安全 bean 访问控制所需的代理对象。
Cglib2AopProxy
类实现了两个名为 AOPProxy
和 MethodInterceptor
的接口。AOPProxy
接口由 Spring 框架提供,表示您希望进行代理的实际 bean,因此它与您的 bean 公开相同的方法。MethodInterceptor
接口也源于 AOP 框架,它包含的方法可以在用户试图访问您已执行代理的 bean 时接受控制权。这意味着 MethodInterceptor
接口处理来自用户的请求以访问执行过代理的 bean。由于 Cglib2AopProxy
类同时实现了 AOPProxy
和 MethodInterceptor
接口,因此它提供了完整的功能,既可以提供经过代理的 bean,也可以处理用户请求以访问代理 bean(参见 参考资料小节 中有关 AOP 的讨论文章的链接)。
执行完前面的步骤后,您现在具有了所需的代理对象。因此 XMLWebApplicationContext
类将安全 bean 的代理(而不是实际的 bean)保存在 “使用 Spring 创建 Java 对象” 的步骤 7 中的同一个数组中。
访问执行过代理的 Java 对象
在前面的几节中,您了解了 Spring 如何创建公有 bean 和私有 bean。出于本文的目的,您可将公有 bean 视为使用代理保护的不安全的私有 bean。现在我们来看一下客户机应用程序为访问公有 bean 和私有 bean 而必须遵循的一系列步骤。
清单 3 展示了 publicCatalog
和 privateCatalog
两个 bean 的 XML 配置。publicCatalog
bean 意味着公共访问,因此不需要使用 bean 代理。privateCatalog
bean 意味着只能由指定用户访问,因此必须加以保护。我在清单 3 中包含了 privateCatalog
bean 的 bean 代理配置:
清单 3. publicCatalog 和 privateCatalog bean 的 XML 配置
<beans>
<bean id="publicCatalog" class="sample.PublicCatalog"/>
<bean id="privateCatalog" class="sample.PrivateCatalog"/>
<!-- proxy configuration for privateCatalog bean -->
<bean id="proxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="beanNames">
<list>
<value>privateCatalog</value>
<!--Names of other beans to be proxied -->
</list>
</property>
<property name="interceptorNames">
<list>
<value>privateCatalogSecurityInterceptor</value>
</list>
</property>
</bean>
<beans>
|
应用程序可以使用清单 4 中的代码访问清单 3 中配置的 publicCatalog
和 privateCatalog
Java bean。注意,清单 4 中显示的 Java 代码可位于 JSP 页面或位于服务器端 Java 应用程序的 bean 中。
清单 4. 访问安全和不安全 Java bean 的客户机应用程序代码
//Step 1: Fetching an instance of the application context
XMLWebApplicationContext applicationCtx =
WebApplicationContextUtils.getWebApplicationContext(
this.getServletConfig().getServletContext());
//Step 2: Fetching an insecure bean from the application context
PublicCatalog publicCatalog =
(PublicCatalog) applicationCtx.getBean("publicCatalog");
//Step 3: Calling a method of the insecure bean
String publicData = publicCatalog.getData();
//Step 4: Fetching a secure bean from the application context
PrivateCatalog privateCatalog =
(PrivateCatalog) applicationCtx.getBean("privateCatalog");
//Step 5: Calling a method of the secure bean
String privateData = privateCatalog.getData();
|
下面将进一步讨论清单 4 中的步骤:
- 步骤 1:取回一个应用程序上下文实例
当应用程序希望访问 XML 配置文件中配置的 Java bean 时,它必须取回您在 “使用 Spring 创建 Java 对象” 的步骤 4 中见到的 XMLWebApplicationContext
对象。XMLWebApplicationContext
对象包含对 XML 配置文件配置的所有 Java beans 的引用。
- 步骤 2:从应用程序上下文中取回不安全的 bean
您现在具有一个对 XMLWebApplicationContext
对象的引用。XMLWebApplicationContext
类公开了一个 getBean()
方法,它包含 bean 的名称并在数组中查找 “使用 Spring 创建 Java 对象” 步骤 7 中准备的 bean。在本例中,该 bean 为 publicCatalog
(未执行过代理),因此 XMLWebApplicationContext
将返回实际的 bean。
- 步骤 3:调用不安全 bean 的方法
现在您可以调用步骤 2 中获得的 publicCatalog
bean 的任何方法。例如,清单 4 显示的 getData()
方法调用的执行没有应用任何访问控制并向应用程序返回类别数据。
- 步骤 4:从应用程序上下文取回安全 bean
安全 bean 与不安全 bean 的取回方式类似,惟一区别是:当您通过调用 getBean()
方法尝试取回安全 bean 时,您将获得安全对象的代理而不是实际的对象。该代理就是我在 “Spring IOC 发挥效用” 步骤 4 中解释的由 Spring 框架创建的同一个对象。
- 步骤 5:调用安全 bean 的方法
当调用安全 bean 的方法时,您在 步骤 4 中获得的代理对象将一个方法调用请求分配给拦截器。拦截器将检查试图访问方法的用户是否具有相应的访问权,从而处理方法调用请求。
您现在应该对 Spring 框架如何创建 Java 对象以及客户机应用程序如何与之交互有了清晰的了解。了解了这些内容后,就更加容易理解并利用 Acegi 的方法安全性拦截器,下一节将具体介绍该主题。
配置 Acegi 的方法安全性拦截器
只要应用程序试图访问由 Acegi 安全系统保护的 bean 方法,请求将被自动传递到 Acegi 的方法安全性拦截器。方法安全性拦截器的作用就是控制对安全 Java bean 的方法的访问。拦截器使用 Acegi 的身份验证和授权框架确认用户是否具有权利调用安全 Java bean 的方法,然后相应地作出响应。
清单 5 展示 Acegi 的方法安全性拦截器的示例配置:
清单 5. Acegi 的方法安全性拦截器的示例配置
<bean id="privateCatalogSecurityInterceptor"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="objectDefinitionSource">
<value>
sample.PrivateCatalog.getData=ROLE_HEAD_OF_ENGINEERING
<!-- Roles required by other beans -->
</value>
</property>
</bean>
|
清单 5 所示的拦截器配置包含三个需要进行配置的属性,可以保护对 Java bean 的访问:authenticationManager
、accessDecisionManager
和 objectDefinitionSource
。
回忆一下,您在本系列第 1 部分的 配置身份验证处理过滤器 中曾对 authenticationManager
属性进行了配置。authenticationManager
属性的作用是对用户进行身份验证。
您在本系列的第二篇文章中了解了 accessDecisionManager 属性。这个访问决策管理器负责制定授权决策。在允许对一个安全 bean 进行访问之前,方法安全拦截器使用 authenticationManager
和 accessDecisionManager
属性对用户进行身份验证和授权。
现在查看 清单 5 中配置的 objectDefinitionSource
属性。它类似于第 1 部分中出现的 objectDefinitionSource 属性。以前的 objectDefinitionSource 包含类似于 /protected/*
和 /**
这样的 URL,清单 5 中的 objectDefinitionSource
属性指定类和方法名;例如,sample.PrivateCatalog
是之前执行过代理的类的名称,而 getData
是您希望对其控制用户访问的方法的名字。
当用户访问 PrivateCatalog
bean 的 getData()
方法时,控制权将自动传递给拦截器。拦截器使用 Acegi 框架检查用户的业务角色是否为 ROLE_HEAD_OF_ENGINEERING
(特定于本文的示例)。如果是的话,拦截器将允许对 getData()
方法进行访问。如果拦截器发现用户角色不是 ROLE_HEAD_OF_ENGINEERING
,则拒绝访问。
下一节将查看一个示例 Acegi 应用程序,它将实现您目前所了解的所有概念。
示例 Acegi 应用程序
本文的 下载源代码 包含了一个名为 AcegiMethodSecurity 的示例应用程序,可按照以下方法进行配置和部署:
- 使用用户信息填充 LDAP 服务器。下载的示例应用程序 包含一个 LDIF 文件,其中含有预备装载到 LDAP 服务器的用户信息。关于如何将 LDIF 文件导入到 LDAP 服务器,请参考第 2 部分的 “填充服务器” 一节。注意,该应用程序涉及与第 2 部分相同的用户(
alice
、bob
和 specialUser
)。
- 将本文下载源代码中的 acegiMethodSecurity.war 文件复制到 Tomcat 安装目录中的 webapps 目录。
- 将 Acegi 的 jar 文件复制到示例应用程序的 WEB-INF/lib 文件夹。(有关内容请参考第 1 部分的 “部署和运行应用程序” 一节。 )
- 下载 cglib-full-2.0.2.jar 文件并将其复制到示例应用程序的 WEB-INF/lib 文件夹。
启动 Tomcat 并尝试运行示例应用程序。
运行示例应用程序
通过从浏览器访问 http://localhost:8080/acegiMethodSecurity URL 可调用示例应用程序。AcegiMethodSecurity 显示的索引页面包含两个链接(Catalog 和 Login),如图 2 所示:
图 2. 示例应用程序的主页面
当单击应用程序的 Catalog 链接时,它将要求您进行登录。如果以 alice
或 specialUser
的身份进行登录,示例应用程序将提供完整的 类别,包括公有数据和私有数据。这是因为在 清单 5 中,您对方法安全性拦截器进行了配置,允许用户使用 ROLE_HEAD_OF_ENGINEERING
访问私有类别,而 alice
和 specialUser
都具有该访问权。另一方面,如果您以 bob
的身份登录,示例应用程序将仅显示公有数据。
为通过身份验证的用户分配额外角色
本节将演示经过增强的示例应用程序。增强后的示例应用程序将展示 Acegi 如何使您能够在运行时向通过身份验证的用户临时分配额外角色。
当安全 bean(例如 清单 3 的 privateCatalog
bean)要访问一个原创资源时,您可能需要使用额外的角色。例如,您可能考虑到您的安全 bean 需要通过 Java 的 Remote Method Invocation (RMI) 框架或一个 Web 服务访问某个远程应用程序。访问安全 bean 的用户不会占用远程应用程序要求访问用户所具备的业务角色。
在本例中,Acegi 首先检查用户是否经过授权来访问安全 bean。之后,Acegi 允许用户访问安全 bean。当安全 bean 试图访问远程服务时,它需要使用额外的业务角色。如果访问安全 bean 的用户不具备额外角色,安全 bean 就不能成功访问远程服务。
run-as-replacement 机制
Acegi 框架提供了一种名为 run-as-replacement 的简单机制,允许您仅在方法调用期间为通过身份验证的用户配置一个或多个额外角色。您可以使用 run-as-replacement 机制为访问远程应用程序的安全 bean 配置额外角色。这意味着只要安全 bean 需要访问远程应用程序,Acegi 将为用户装载额外角色,从而允许安全 bean 访问远程应用程序。
清单 6 对 清单 5 中的方法安全性拦截器的配置进行了增强。增强后的配置使用了 run-as-replacement 机制。
清单 6. Acegi 方法安全性拦截器的增强配置
<bean id="privateCatalogSecurityInterceptor"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager">
<ref bean="authenticationManager"/>
</property>
<property name="accessDecisionManager">
<ref bean="accessDecisionManager"/>
</property>
<property name="runAsManager">
<bean id="runAsManager"
class="org.acegisecurity.runas.RunAsManagerImpl">
<property name="key">
<value>myKeyPass</value>
</property>
</bean>
</property>
<property name="objectDefinitionSource">
<value>
sample.PrivateCatalog.getData=ROLE_HEAD_OF_ENGINEERING,RUN_AS_MANAGER
</value>
</property>
</bean>
|
清单 6 使用粗体显示了两处增强(与 清单 5 相比)。第一处增强为 runAsManager
属性。runAsManager
属性的作用是向通过身份验证的用户动态添加角色。出于这个目的,runAsManager
属性包含了 RunAsManagerImpl
bean 的定义。RunAsManagerImpl
bean 只有在满足下面的条件时才可变为活跃状态:在 objectDefinitionSource
方法的角色定义中找到以 RUN_AS_
为前缀的角色。例如,PrivateCatalog.getData()
方法的角色定义(清单 6 中以粗体显示的第二处增强)具有一个 RUN_AS_MANAGER
角色。
RunAsManagerImpl
bean 包含一个名为 key
的属性,它封装的加密键用于确保只将额外的角色作为 run-as-replacement 程序的一部分生成。
当用户调用 getData()
方法时,RunAsManagerImpl
bean 变为活跃状态并创建名为 RUN_AS_MANAGER
的额外角色,从而启用 getData()
方法访问远程应用程序。
增强的方法安全性
本文的 下载源代码 包含一个名为 EnhancedAcegiMethodSecurity
的示例应用程序,它可以演示 run-as-replacement 机制和程序。该应用程序将显示一个具有 Catalog 链接的索引页面。如果单击 Catalog 链接,将要求进行登录。
登录后,EnhancedAcegiMethodSecurity
应用程序将为您提供登录用户及其角色的完整信息。例如,如果以 alice
或 specialUser
身份登录,将向您显示用户的所有业务角色,包括额外创建的临时的 RUN_AS_MANAGER
角色。
结束语
在这份共分三部分的系列文章中,我介绍了如何使用 Acegi 安全系统增强基于 URL 的安全性和基于方法的安全性。您了解了如何设计访问控制策略并将它们托管在目录服务器中,如何对 Acegi 进行配置以与目录服务器进行通信,以及如何根据托管在服务器的访问控制策略制定身份验证和授权决策。
本系列的最后一篇文章主要介绍使用基于方法的安全性保护 Java 类实例。文章还解释了 Acegi 和 Spring 如何在内部创建和代理 Java 对象以及 bean 代理如何实现访问控制。文章包含了两个示例应用程序,您可以使用它们进一步研究本系列中学到的概念,更多有关使用 Acegi 保护 Java 应用程序的内容,请参见 参考资料 小节。
来自:http://www-128.ibm.com/developerworks/cn/java/j-acegi3/?
2007 年 6 月 21 日
了解了 Acegi 安全系统(Acegi Security System)的 基础知识 后,我们将介绍该系统的更加高级的应用。在本文中,Bilal Siddiqui 向您展示了如何结合使用 Acegi 和一个 LDAP 目录服务器,实现灵活的具有高性能的 Java™ 应用程序的安全性。还将了解如何编写访问控制策略并将其存储在 ApacheDS 中,然后配置 Acegi 使其与目录服务器交互,从而实现身份验证和授权的目的。
这期共分三部分的系列文章介绍了如何使用 Acegi 安全系统保护 Java 企业应用程序。在 本系列第一篇文章 中,我介绍了 Acegi 并解释了如何使用安全过滤器实现一个简单的基于 URL 的安全系统。在第二篇文章中,我将讨论 Acegi 的更加高级的应用,首先我将编写一个访问控制策略并将其存储在 ApacheDS 中,ApacheDS 是一个开源的 LDAP 目录服务器。我还将展示配置 Acegi 的方法,使它能够与目录服务器交互并实现您的访问控制策略。本文的结尾提供了一个示例应用程序,它使用 ApacheDS 和 Acegi 实现了一个安全的访问控制策略。
实现访问控制策略通常包含两个步骤:
- 将有关用户和用户角色的数据存储在目录服务器中。
- 编写安全代码,它将定义有权访问并使用数据的人员。
Acegi 将减轻代码编写的工作,因此在这篇文章中,我将展示如何将用户和用户角色信息存储到 ApacheDS 中,然后实现这些信息的访问控制策略。在该系列的最后一篇文章中,我将展示如何配置 Acegi,实现对 Java 类的安全访问。
您可以在本文的任何位置 下载样例应用程序。参见 参考资料 下载 Acegi、Tomcat 和 ApacheDS,您需要使用它们运行样例代码和示例应用程序。
LDAP 基础知识
轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP)可能是最流行的一种定义数据格式的协议,它针对常见的目录操作,例如对存储在目录服务器中的信息执行的读取、编辑、搜索和删除操作。本节将简要解释为什么目录服务器是属性文件存储安全信息的首选,并展示如何在 LDAP 目录中组织和托管用户信息。
为什么要使用目录服务器?
本系列第一部分向您介绍了一种简单的方法,可以将用户信息以属性文件的形式保存起来(参见 第 1 部分,清单 6)。属性文件以文本格式保存用户名、密码和用户角色。对于大多数真实应用程序而言,使用属性文件存储安全信息远远不够。各种各样的理由表明,目录服务器通常都是更好的选择。其中一个原因是,真实的企业应用程序可以被大量用户访问 —— 通常是几千名用户,如果应用程序将其部分功能公开给用户和供应商时更是如此。频繁搜索文本文件中随意存储的信息,这样做的效率并不高,但是目录服务器对这类搜索进行了优化。
第 1 部分的清单 6 中的属性文件演示了另一个原因,该文件组合了用户和角色。在真实的访问控制应用程序中,您通常都需要分别定义和维护用户和角色信息,这样做可以简化用户库的维护。目录服务器为更改或更新用户信息提供了极大的灵活性,例如,反映职位升迁或新聘用人员。参见 参考资料 以了解更多关于目录服务器的使用及其优点的信息。
LDAP 目录设置
如果希望将用户信息存储在一个 LDAP 目录中,您需要理解一些有关目录设置的内容。本文并没有提供对 LDAP 的完整介绍(参见 参考资料),而是介绍了一些在尝试结合使用 Acegi 和 LDAP 目录之前需要了解的基本概念。
LDAP 目录以节点树的形式存储信息,如图 1 所示:
图 1. LDAP 目录的树状结构
在图 1 中,根节点的名称为 org
。根节点可以封装与不同企业有关的数据。例如,本系列第 1 部分开发的制造业企业被显示为 org
节点的直接子节点。该制造业企业具有两个名为 departments
和 partners
的子节点。
partners
子节点封装了不同类型的合作伙伴。图 1 所示的三个分别为 customers
、employees
和 suppliers
。注意,这三种类型的合作伙伴其行为与企业系统用户一样。每一种类型的用户所扮演的业务角色不同,因此访问系统的权利也不同。
类似地,departments
节点包含该制造业企业的不同部门的数据 —— 例如 engineering
和 marketing
字节点。每个部门节点还包含一组或多组用户。在 图 1 中,engineers
组是 engineering
部门的子节点。
假设每个部门的子节点表示一组用户。因此,部门节点的子节点具有不同的用户成员。例如,设计部门的所有工程师都是 engineering
部门内 engineers
组的成员。
最后,注意 图 1 中 departments
节点的最后一个子节点。specialUser
是一名用户,而非一组用户。在目录设置中,像 alice
和 bob
之类的用户一般都包含在 partners
节点中。我将这个特殊用户包含在 departments
节点中,以此证明 Acegi 允许用户位于 LADP 目录中任何地点的灵活性。稍后在本文中,您将了解如何配置 Acegi 以应用 specialUser
。
使用专有名称
LDAP 使用专有名称(DN)的概念来识别 LDAP 树上特定的节点。每个节点具有惟一的 DN,它包含该节点完整的层次结构信息。例如,图 2 展示了图 1 中的一些节点的 DN:
图 2. LDAP 目录节点的专有名称
首先,注意图 2 中根节点的 DN。它的 DN 为 dc=org
,这是与 org
根节点相关的属性值对。每个节点都有若干个与之相关的属性。dc
属性代表 “domain component” 并由 LDAP RFC 2256 定义(参见 参考资料 中有关官方 RFC 文档的链接),LDAP 目录中的根节点通常表示为一个域组件。
每个 LDAP 属性是由 RFC 定义的。LDAP 允许使用多个属性创建一个 DN,但是本文的示例只使用了以下 4 个属性:
dc
(域组件)
o
(组织)
ou
(组织单元)
uid
(用户 ID)
示例使用 dc
表示域,用 o
表示组织名称,ou
表示组织的不同单元,而 uid
表示用户。
由于 org
是根节点,其 DN 只需指定自身的名称(dc=org
)。比较一下,manufacturingEnterprise
节点的 DN 是 o=manufacturingEnterprise,dc=org
。当向下移动节点树时,每个父节点的 DN 被包含在其子节点的 DN 中。
将属性分组
LDAP 将相关的属性类型分到对象类中。例如,名为 organizationalPerson
的对象类所包含的属性定义了在组织内工作的人员(例如,职称、常用名、邮寄地址等等)。
对象类使用继承特性,这意味着 LDAP 定义了基类来保存常用属性。然后子类再对基类进行扩展,使用其定义的属性。LDAP 目录中的单个节点可以使用若干个对象类:本文的示例使用了以下几个对象类:
- top 对象类是 LDAP 中所有对象类的基类。
- 当其他对象类都不适合某个对象时,将使用 domain 对象类。它定义了一组属性,任何一个属性都可以用来指定一个对象。其
dc
属性是强制性的。
- organization 对象类表示组织节点,例如 图 2 中的
manufacturingEnterprise
。
- organizationalUnit 对象类表示组织内的单元,例如 图 1 中的
departments
节点及其子节点。
- groupOfNames 对象类表示一组名称,例如某部门职员的名称。它具有一个
member
属性,该属性包含一个用户列表。图 1 中所有的组节点(例如 engineers
节点)使用 member
属性指定该组的成员。而且,示例使用 groupOfNames
对象类的 ou
(组织单元)属性指定组用户的业务角色。
- organizationalPerson 对象类表示组织内某个职员(例如 图 1 中的
alice
节点)。
使用 LDAP 服务器
在真实的应用程序中,通常将有关系统用户的大量信息托管在一个 LDAP 目录中。例如,将存储每个用户的用户名、密码、职称、联系方式和工资信息。为简单起见,下面的例子将只向您展示如何保存用户名和密码。
如前所述,示例使用 ApacheDS(一种开源的 LDAP 目录服务器)演示了 Acegi 是如何使用 LDAP 目录的。示例还使用了一个开源的 LDAP 客户机(名为 JXplorer)执行简单的目录操作,例如将信息托管在 ApacheDS 上。参见 参考资料 以下载 ApacheDS、JXplorer 并了解更多有关两者协作的信息。
在 ApacheDS 创建一个根节点
要创建 图 1 所示的节点树,必须首先在 ApacheDS 中创建一个根节点 org
。ApacheDS 为此提供了一个 XML 配置文件。XML 配置文件定义了一组可进行配置的 bean,从而根据应用程序的需求定制目录服务器的行为。本文只解释创建根节点所需的配置。
可以在 ApacheDS 安装中的 conf
文件夹找到名为 server.xml
的 XML 配置文件。打开文件后,会发现很多 bean 配置类似于 Acegi 的过滤器配置。查找名为 examplePartitionsConfiguration
的 bean。该 bean 控制 ApacheDS 上的分区。当创建新的根节点时,实际上将在 LDAP 目录上创建一个新的分区。
编辑 examplePartitionConfiguration
bean 以创建 org
根节点,如清单 1 所示:
清单 1. 编辑模式的 examplePartitionConfiguration bean 配置
<bean id="examplePartitionConfiguration" class=
"org.apache.directory.server.core.partition.impl.btree.MutableBTreePartitionConfiguration"
>
<property name="suffix"><value>dc=org</value></property>
<property name="contextEntry">
<value>
objectClass: top
objectClass: domain
dc: org
</value>
</property>
<!-- Other properties of the examplePartitionConfiguration bean, which you don't
need to edit. -->
</bean>
|
清单 1 编辑了 examplePartitionConfiguration
bean 的两个属性:
- 一个属性名为
suffix
,它定义根条目的 DN。
- 另一个属性名为
contextEntry
,定义 org
节点将使用的对象类。注意,org
根节点使用两个对象类:top
和 domain
。
本文的 源代码下载 部分包含了编辑模式的 server.xml 文件。如果希望继续学习本示例,请将 server.xml 文件从源代码中复制到您的 ApacheDS 安装目录中的正确位置,即 conf
文件夹。
图 3 所示的屏幕截图展示了在 ApacheDS 中创建根节点后,JXplorer 是如何显示该根节点的:
图 3. JXplorer 显示根节点
填充服务器
设置 LDAP 服务器的下一步是使用用户和组信息填充服务器。您可以使用 JXplorer 在 ApacheDS 中逐个创建节点,但是使用 LDAP Data Interchange Format (LDIF) 填充服务器会更加方便。LDIF 是可被大多数 LDAP 实现识别的常见格式。developerWorks 文章很好地介绍了 LDIF 文件的内容,因此本文将不再做详细说明。(参见 参考资料 中有关 LDIF 的详细资料。)
您可以在 源代码下载 部分查看 LDIF 文件,它表示 图 1 所示的用户和部门。您可以使用 JXplorer 将 LDIF 文件导入到 ApacheDS。要导入 LDIF 文件,在 JXplorer 中使用 LDIF
菜单,如图 4 所示:
图 4. 将 LDIF 文件导入到 ApacheDS
将 LDIF 文件导入到 ApacheDS 之后,JXplorer 将显示用户节点和部门节点树,如 图 1 所示。现在您可以开始配置 Acegi,使其能够与您的 LDAP 服务器通信。
为 LDAP 实现配置 Acegi
回想一下第 1 部分,其中 Acegi 使用身份验证处理过滤器(Authentication Processing Filter,APF)进行身份验证。APF 执行所有后端身份验证处理任务,例如从客户机请求中提取用户名和密码,从后端用户库读取用户参数,以及使用这些信息对用户进行身份验证。
您在第 1 部分中为属性文件实现配置了 APF,现在您已将用户库存储在 LDAP 目录中,因此必须使用不同的方式配置过滤器来和 LDAP 目录进行通信。首先看一下清单 2,它展示了在第 1 部分中的 “Authentication Processing Filter” 一节中如何为属性文件实现配置 APF 过滤器:
清单 2. 为属性文件配置 APF
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager" ref="authenticationManager" />
<property name="authenticationFailureUrl"
value="/login.jsp?login_error=1" />
<property name="defaultTargetUrl"
value="/index.jsp" />
<property name="filterProcessesUrl"
value="/j_acegi_security_check" />
</bean>
|
查看一下清单 2,您曾经为 APF 提供了 4 个参数。您只需在 LDAP 服务器中为存储重新配置第一个参数(authenticationManager
)即可。其他三个参数保持不变。
配置身份验证管理器
清单 3 展示了如何配置 Acegi 的身份验证管理器,以实现与 LDAP 服务器的通信:
清单 3. 为 LDAP 配置 Acegi 的身份验证管理器
<bean id="authenticationManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref local="ldapAuthenticationProvider" />
</list>
</property>
</bean>
|
在清单 3 中,org.acegisecurity.providers.ProviderManager
是一个管理器类,它管理 Acegi 的身份验证过程。为此,身份验证管理器需要一个或多个身份验证提供者。您可以使用管理器 bean 的提供者属性来配置一个或多个提供者。清单 3 只包含了一个提供者,即 LDAP 身份验证提供者。
LDAP 身份验证提供者处理所有与后端 LDAP 目录的通信。您必须对其进行配置,下一节内容将讨论该主题。
配置 LDAP 身份验证提供者
清单 4 展示了 LDAP 身份验证提供者的配置:
清单 4. 配置 LDAP 身份验证提供者
<bean id="ldapAuthenticationProvider"
class="org.acegisecurity.providers.ldap.LdapAuthenticationProvider">
<constructor-arg><ref local="authenticator"/></constructor-arg>
<constructor-arg><ref local="populator"/></constructor-arg>
</bean>
|
注意 LDAP 身份验证提供者类的名称为 org.acegisecurity.providers.ldap.LdapAuthenticationProvider
。其构造函数包含两个参数,使用两个 <constructor-arg>
标记的形式,如清单 4 所示。
LdapAuthenticationProvider
构造函数的第一个参数是 authenticator
,该参数通过检查用户的用户名和密码对 LDAP 目录的用户进行身份验证。完成身份验证后,第二个参数 populator
将从 LDAP 目录中检索有关该用户的访问权限(或业务角色)信息。
以下小节将向您展示如何配置验证器和填充器 bean。
配置验证器
authenticator
bean 将检查具有给定用户名和密码的用户是否存在于 LDAP 目录中。Acegi 提供了名为 org.acegisecurity.providers.ldap.authenticator.BindAuthenticator
的验证器类,它将执行验证用户名和密码所需的功能。
配置 authenticator
bean,如清单 5 所示:
清单 5. 配置验证器 bean
<bean id="authenticator"
class="org.acegisecurity.providers.ldap.authenticator.BindAuthenticator">
<constructor-arg><ref local="initialDirContextFactory"/></constructor-arg>
<property name="userDnPatterns">
<list>
<value>uid={0},ou=employees,ou=partners</value>
<value>uid={0},ou=customers,ou=partners</value>
<value>uid={0},ou=suppliers,ou=partners</value>
</list>
</property>
<property name="userSearch"><ref local="userSearch"/></property>
</bean>
|
在清单 5 中,BindAuthenticator
构造函数具有一个参数,使用 <constructor-arg>
标记的形式。清单 5 中参数的名称为 initialDirContextFactory
。该参数实际上是另一个 bean,稍后您将学习如何配置该 bean。
目前为止,只知道 initialDirContextFactory
bean 的作用就是为稍后的搜索操作指定初始上下文。初始上下文是一个 DN,它指定了 LDAP 目录内某个节点。指定初始上下文后,将在该节点的子节点中执行所有的搜索操作(例如查找特定用户)。
例如,回到 图 2 中查看 partners
节点,它的 DN 是 ou=partners,o=manufacturingEnterprise,dc=org
。如果将 partners
节点指定为初始上下文,Acegi 将只在 partners
节点的子节点中查找用户。
指定 DN 模式
除配置 BindAuthenticator
构造函数外,还必须配置 authenticator
bean 的两个属性(清单 5 中的两个 <property>
标记)。
第一个 <property>
标记定义了一个 userDnPatterns
属性,它封装了一个或多个 DN 模式列表。DN 模式 指定了一组具有类似特性的 LDAP 节点(例如 图 2 所示的 employees
节点的所有子节点)。
Acegi 的身份验证器从 authenticator
bean 的 userDnPatterns
属性中配置的每个 DN 模式构造了一个 DN。例如,查看 清单 5 中配置的第一个模式,即 uid={0},ou=employees,ou=partners
。在进行身份验证的时候,authenticator
bean 使用用户提供的用户名(比如 alice
)替换了 {0}
。使用用户名取代了 {0}
之后,DN 模式将变为相对 DN(RDN)uid=alice,ou=employees,ou=partners
,它需要一个初始上下文才能成为 DN。
例如,查看 图 2 中的 alice's
条目。该条目是 employees
节点的第一个子节点。它的 DN 是 uid=alice,ou=employees,ou=partners,o=manufacturingEnterprise, dc=org
。如果使用 o=manufacturingEnterprise,dc=org
作为初始上下文并将其添加到 RDN uid=alice,ou=employees,ou=partners
之后,将获得 alice 的 DN。
使用这种方法通过 DN 模式构建了用户的 DN 后,authenticator
将把 DN 和用户密码发送到 LDAP 目录。目录将检查该 DN 是否具有正确的密码。如果有的话,用户就可以通过身份验证。这个过程在 LDAP 术语中被称为 bind 身份验证。LDAP 还提供了其他类型的身份验证机制,但是本文的示例只使用了 bind 身份验证。
如果目录中并没有第一个 DN 模式创建的 DN,authenticator
bean 尝试使用列表中配置的第二个 DN 模式。依此类推,authenticator
bean 将尝试所有的 DN 模式来为进行身份验证的用户构造正确的用户 DN。
搜索过滤器
回想一下较早的章节 “LDAP 目录设置”,我在将用户信息存储到 LDAP 目录时添加了一点灵活性。方法是在 图 1 所示的 departments
节点内创建一个特定用户(specialUser
)。
如果试图使用 清单 5 中配置的任何一种 DN 模式创建特定用户的 DN,您会发现没有一种 DN 模式可用。因此,当用户尝试登录时,Acegi 的 authenticator
bean 将不能够构造正确的 DN,从而无法对该用户进行身份验证。
通过允许您指定搜索过滤器,Acegi 能够处理类似的特殊情况。身份验证器 bean 使用搜索过滤器查找不能够通过 DN 模式构造 DN 进行身份验证的用户。
清单 5 中的第二个 <property>
标记具有一个 <ref>
子标记,它引用名为 userSearch
的 bean。userSearch
bean 指定搜索查询。清单 6 展示了如何配置 userSearch
bean 来处理特定用户:
清单 6. 配置搜索查询以搜索特定用户
<bean id="userSearch"
class="org.acegisecurity.ldap.search.FilterBasedLdapUserSearch">
<constructor-arg>
<value>ou=departments</value>
</constructor-arg>
<constructor-arg>
<value>(uid={0})</value>
</constructor-arg>
<constructor-arg>
<ref local="initialDirContextFactory" />
</constructor-arg>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
|
搜索查询的参数
清单 6 展示了 userSearch
bean 是 org.acegisecurity.ldap.search.FilterBasedLdapUserSearch
类的一个实例,该类的构造函数具有三个参数。第一个参数指定 authenticator
在哪个节点中搜索用户。第一个参数的值为 ou=departments
,该值是一个 RDN,指定了 图 2 所示的 departments
节点。
第二个参数 (uid={0})
指定了一个搜索过滤器。由于使用 uid
属性指定用户,因此可以通过查找 uid
属性具有特定值的节点来查找用户。正如您所料,花括号里面的 0 向 Acegi 表示使用进行身份验证的用户的用户名(本例中为 specialUser
)替换 {0}
。
第三个参数是对讨论 清单 5 中的 BindAuthenticator
构造函数时引入的相同初始上下文的引用。回想一下,当指定了初始上下文后,稍后将在该初始上下文节点的子节点内进行所有的搜索操作。注意,应将指定为 清单 5 中第一个参数(ou=departments
)的值的 RDN 前加到初始上下文。
除了这三个构造器参数,清单 6 所示的 userSearch
bean 还具有一个名为 searchSubtree
的属性。如果将其值指定为 true
,搜索操作将包括节点的子树(即所有子节点、孙节点、孙节点的子节点等),该节点被指定为构造函数的第一个参数的值。
authenticator
bean 的配置完成后,下一步将查看 populator
bean 的配置,如 清单 4 所示。
配置 populator
populator
bean 将读取已经通过 authenticator
bean 身份验证的用户的业务角色。清单 7 展示 populator
bean 的 XML 配置:
清单 7. populator bean 的 XML 配置
<bean id="populator"
class="org.acegisecurity.providers.ldap.populator.DefaultLdapAuthoritiesPopulator">
<constructor-arg>
<ref local="initialDirContextFactory"/>
</constructor-arg>
<constructor-arg>
<value>ou=departments</value>
</constructor-arg>
<property name="groupRoleAttribute">
<value>ou</value>
</property>
<property name="searchSubtree">
<value>true</value>
</property>
</bean>
|
在清单 7 中,populator
bean 的构造函数包括 2 个参数,以及一个 groupRoleAttribute
属性。构造函数的第一个参数指定了 populator
bean 用来读取经过验证用户的业务角色的初始上下文。并不强制要求 authenticator
和 populator
bean 使用相同的初始上下文。您可以为这两者分别配置一个初始上下文。
第二个构造函数参数指定了 populator 前加到初始上下文的 RDN。因此,RDN 组成了包含组用户的节点的 DN,例如 departments
节点。
populator
bean 的 groupRoleAttribute
属性指定了持有组成员业务角色数据的属性。回想 设置 LDAP 目录 一节中,您将每组用户的业务角色信息存储在名为 ou
的属性中。然后将 ou
设置为 groupRoleAttribute
属性的值,如 清单 7 所示。
如您所料,populator
bean 将搜索整个 LDAP 目录来查找经过验证的用户所属的组节点。然后读取组节点的 ou
属性的值,获取用户经过授权的业务角色。
这样就完成了 populator
bean 的配置。目前为止,我们在三个位置使用了初始上下文:清单 5、清单 6 和 清单 7。接下来将了解如何配置初始上下文。
配置初始上下文
清单 8 展示了在 Acegi 中配置初始上下文的过程:
清单 8. 初始上下文的 XML 配置
<bean id="initialDirContextFactory"
class="org.acegisecurity.ldap.DefaultInitialDirContextFactory">
<constructor-arg value="ldap://localhost:389/o=manufacturingEnterprise,dc=org"/>
<property name="managerDn">
<value>cn=manager,o=manufacturingEnterprise,dc=org</value>
</property>
<property name="managerPassword">
<value>secret</value>
</property>
</bean>
|
清单 8 中 Acegi 的初始上下文类的名称为 org.acegisecurity.ldap.DefaultInitialDirContextFactory
,这是 Acegi 包含的工厂类。Acegi 在内部使用该类构造其他处理目录操作(例如在整个目录中搜索)的类的对象。当配置初始上下文工厂时,必须指定以下内容:
- 将您的 LDAP 目录和根目录节点的网络地址指定为构造函数的参数。在初始上下文配置的节点将作为根节点。就是说所有后续操作(例如
search
)都将在根节点定义的子树中执行。
- 将 DN 和密码分别定义为
managerDn
和 managerPassword
属性。在执行任何搜索操作之前,Acegi 必须使用目录服务器对 DN 和密码进行身份验证。
您已经了解了如何将用户库托管在 LDAP 目录中,以及如何配置 Acegi 来使用来自 LDAP 目录的信息对用户进行身份验证。下一节将进一步介绍 Acegi 的身份验证处理过滤器,了解新配置的 bean 是如何管理身份验证过程的。
身份验证和授权
APF 配置完成后,将能够与 LDAP 目录进行通信来对用户进行身份验证。如果您阅读过第 1 部分,那么对与目录通信过程中 APF 执行的一些步骤不会感到陌生,我在第 1 部分中向您展示了过滤器如何使用不同的服务进行用户身份验证。图 5 所示的序列表与您在 第 1 部分图 3 看到的非常类似:
图 5. APF 对一名 LDAP 用户进行身份验证
无论 APF 使用属性文件进行内部的身份验证还是与 LDAP 服务器进行通信,步骤 1 到步骤 9 与第 1 部分是相同的。这里简单描述了前 9 个步骤,您可以从步骤 10 开始继续学习特定于 LDAP 的事件:
- 过滤器链前面的过滤器将请求、响应和过滤器链对象传递给 APF。
- APF 使用取自请求对象的用户名、密码和其他信息创建一个身份验证标记。
- APF 将身份验证标记传递给身份验证管理器。
- 身份验证管理器可能包含一个或多个身份验证提供者。每个提供者恰好支持一种身份验证类型。管理器将检查哪一种提供者支持从 APF 接收到的身份验证标记。
- 身份验证管理器将身份验证标记传递给适合该类型身份验证的提供者。
- 身份验证提供者从身份验证标记中提取用户名并将其传递到名为 user cache service 的服务。Acegi 缓存了已经进行过身份验证的用户。该用户下次登录时,Acegi 可以从缓存中加载他或她的详细信息(比如用户名、密码和权限),而不是从后端数据存储中读取数据。这种方法使得性能得到了改善。
- user cache service 检查用户的详细信息是否存在于缓存中。
- user cache service 将用户的详细信息返回给身份验证提供者。如果缓存不包含用户详细信息,则返回 null。
- 身份验证提供者检查缓存服务返回的是用户的详细信息还是 null。
- 从这里开始,身份验证处理将特定于 LDAP。 如果缓存返回 null,LDAP 身份验证提供者将把用户名(在步骤 6 中提取的)和密码传递给 清单 5 中配置的
authenticator
bean。
authenticator
将使用在 清单 5 的 userDnPatterns
属性中配置的 DN 模式创建用户 DN。通过从一个 DN 模式中创建一个 DN,然后将该 DN 和用户密码(从用户请求中获得)发送到 LDAP 目录,它将逐一尝试所有可用的 DN 模式。LDAP 目录将检查该 DN 是否存在以及密码是否正确。如果其中任何一个 DN 模式可行的话,用户被绑定到 LDAP 目录中,authenticator
将继续执行步骤 15。
- 如果任何一种 DN 模式都不能工作的话(这意味着在 DN 模式指定的任何位置都不存在使用给定密码的用户),
authenticator
根据 清单 6 配置的搜索查询在 LDAP 目录中搜索用户。如果 LDAP 目录没有找到用户,那么身份验证以失败告终。
- 如果 LDAP 目录查找到了用户,它将用户的 DN 返回到
authenticator
。
authenticator
将用户 DN 和密码发送到 LDAP 目录来检查用户密码是否正确。如果 LDAP 目录发现用户密码是正确的,该用户将被绑定到 LDAP 目录。
authenticator
将用户信息发送回 LDAP 身份验证提供者。
- LDAP 身份验证提供者将控制权传递给
populator
bean。
populator
搜索用户所属的组。
- LDAP 目录将用户角色信息返回给
populator
。
populator
将用户角色信息返回给 LDAP 身份验证提供者。
- LDAP 身份验证提供者将用户的详细信息(以及用户业务角色信息)返回给 APF。用户现在成功进行了身份验证。
不论使用何种身份验证方法,最后三个步骤是相同的(步骤21、21 和 23)。
配置拦截器
您已经了解了 APF 对用户进行身份验证的步骤。接下来是查看成功进行身份验证的用户是否被授权访问所请求的资源。这项任务由 Acegi 的拦截过滤器(Interceptor Filter,IF)完成。本节将向您展示如何配置 IF 来实现访问控制策略。
回想一下在 第 1 部分的清单 7 中配置 IF 的步骤。拦截过滤器在资源和角色之间建立映射,就是说只有具备必要角色的用户才能访问给定资源。为了演示制造业企业中不同部门的业务角色,清单 9 向现有的 IF 配置添加了另外的角色:
清单 9. 配置拦截过滤器
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/protected/engineering/**=ROLE_HEAD_OF_ENGINEERING
/protected/marketing/**=ROLE_HEAD_OF_MARKETING
/**=IS_AUTHENTICATED_ANONYMOUSLY
</value>
</property>
</bean>
|
在清单 9 中,IF 包含三个参数。其中第一个和第三个参数与第 1 部分中最初配置的参数相同。这里添加了第二个参数(名为 accessDecisionManager
的 bean)。
accessDecisionManager
bean 负责指定授权决策。它使用清单 9 中第三个参数提供的访问控制定义来指定授权(或访问控制)决策。第三个参数是 objectDefinitionSource
。
配置访问决策管理器
accessDecisionManager
决定是否允许一个用户访问某个资源。Acegi 提供了一些访问决策管理器,它们指定访问控制决策的方式有所不同。本文只解释了其中一种访问决策管理器的工作方式,其配置如清单 10 所示:
清单 10. 配置访问决策管理器
<bean id="accessDecisionManager" class="org.acegisecurity.vote.AffirmativeBased">
<property name="decisionVoters">
<list>
<bean class="org.acegisecurity.vote.RoleVoter"/>
<bean class="org.acegisecurity.vote.AuthenticatedVoter" />
</list>
</property>
</bean>
|
在清单 10 中,accessDecisionManager
bean 是 org.acegisecurity.vote.AffirmativeBased
类的实例。accessDecisionManager
bean 只包含一个参数,即投票者(voter)列表。
在 Acegi 中,投票者确定是否允许某个用户访问特定的资源。当使用 accessDecisionManager
查询时,投票者具有三个选项:允许访问(access-granted)、拒绝访问(access-denied),如果不确定的话则放弃投票(abstain from voting)。
不同类型的访问决策管理器解释投票者决策的方法也有所不同。清单 10 所示的 AffirmativeBased
访问决策管理器实现了简单的决策逻辑:如果任何投票者强制执行肯定投票,将允许用户访问所请求的资源。
投票者逻辑
Acegi 提供了若干个投票者实现类型。accessDecisionManager
将经过验证的用户的信息(包括用户的业务角色信息)和 objectDefinitionSource
对象传递给投票者。本文的示例使用了两种类型的投票者,RoleVoter
和 AuthenticatedVoter
,如清单 10 所示。现在看一下每种投票者的逻辑:
- RoleVoter 只有在
objectDefinitionSource
对象的行中找到以 ROLE_
前缀开头的角色时才进行投票。如果 RoleVoter
没有找到这样的行,将放弃投票;如果在用户业务角色中找到一个匹配的角色,它将投票给允许访问;如果没有找到匹配的角色,则投票给拒绝访问。在 清单 9 中,有两个角色具有 ROLE_
前缀:ROLE_HEAD_OF_ENGINEERING
和 ROLE_HEAD_OF_MARKETING
。
- AuthenticatedVoter 只有在
objectDefinitionSource
对象中找到具有某个预定义角色的行时才进行投票。在 清单 9 中,有这样一行:IS_AUTHENTICATED_ANONYMOUSLY
。匿名身份验证意味着用户不能够进行身份验证。找到该行后,AuthenticatedVoter
将检查一个匿名身份验证的用户是否可以访问某些不受保护的资源(即这些资源没有包含在具备 ROLE_
前缀的行中)。如果 AuthenticatedVoter
发现所请求的资源是不受保护的并且 objectDefinitionSource
对象允许匿名身份验证的用户访问不受保护的资源,它将投票给允许访问;否则就投票给拒绝访问。
示例应用程序
本文提供了一个示例应用程序,它将演示您目前掌握的 LDAP 和 Acegi 概念。LDAP-Acegi 应用程序将显示一个索引页面,该页面将设计和销售文档呈现给合适的经过身份验证的用户。正如您将看到的一样,LDAP-Acegi 应用程序允许用户 alice
查看设计文档,并允许用户 bob
查看销售文档。它还允许特定用户同时查看设计和销售文档。所有这些内容都是在本文开头配置 LDAP 目录服务器时设置的。立即 下载示例应用程序 来开始使用它。
结束语
在本文中,您了解了如何将用户和业务角色信息托管在 LDAP 目录中。您还详细了解了配置 Acegi 的方法,从而与 LDAP 目录交互实现访问控制策略。在本系列最后一期文章中,我将展示如何配置 Acegi 来保护对 Java 类的访问。
来自:http://www.cnblogs.com/amboyna/archive/2008/03/25/1122084.html
2007 年 5 月 08 日
这份共分三部分的系列文章介绍了 Acegi 安全系统(Acegi Security System),它是用于 Java™ 企业应用程序的强大的开源安全框架。在第一篇文章中,Bilal Siddiqui 顾问将向您介绍 Acegi 的架构和组件,并展示如何使用它来保护一个简单的 Java 企业应用程序。
Acegi Security System 是一种功能强大并易于使用的替代性方案,使您不必再为 Java 企业应用程序编写大量的安全代码。虽然它专门针对使用 Spring 框架编写的应用程序,但是任何类型的 Java 应用程序都没有理由不去使用 Acegi。这份共分三部分的系列文章详细介绍了 Acegi,并展示了如何使用它保护简单的企业应用程序以及更复杂的应用程序。
本系列首先介绍企业应用程序中常见的安全问题,并说明 Acegi 如何解决这些问题。您将了解 Acegi 的架构模型及其安全过滤器,后者包含了在保护应用程序中将用到的大多数功能。您还将了解到各个过滤器如何单独进行工作,如何将它们组合起来,以及过滤器如何在一个企业安全实现中将各种功能从头到尾地链接起来。本文最后通过一个样例应用程序演示了基于 URL 安全系统的 Acegi 实现。本系列后续两篇文章将探究 Acegi 的一些更高级的应用,包括如何设计和托管访问控制策略,然后如何去配置 Acegi 以使用这些策略。
您必须 下载 Acegi,这样才能编译本文的示例代码并运行本文的样例应用程序。还必须有作为工作站的一部分运行的 Tomcat 服务器。
企业应用程序安全性
由于企业内容管理(ECM)应用程序管理存储在不同类型数据源(如文件系统、关系数据库和目录服务)中的企业内容的编写和处理,ECM 安全性要求对这些数据源的访问进行控制。比如,一个 ECM 应用程序可能会控制被授权读取、编辑或删除数据的对象,而这些数据和制造业企业的设计、市场营销、生产以及质量控制有关。
在一个 ECM 安全场景中,比较常见的就是通过对企业资源定位符(或网络地址)应用安全性,从而实现访问控制。这种简单的安全模型被称为统一资源定位符 或 URL 安全性。正如我在本文后面(以及本系列后续文章)所演示的一样,Acegi 为实现 URL 安全性提供了全面的特性。
然而,在很多企业场景中,URL 安全性还远远不够。比如,假设一个 PDF 文档包含某个制造业公司生产的特殊产品的数据。文档的一部分包含了将由该公司的设计部门编辑和更新的设计数据。另一部分包含了生产经理将使用的生产数据。对于诸如此类的场景,需要实现更加细粒度的安全性,对文档的不同部分应用不同的访问权限。
本文介绍了 Acegi 为实现 URL 安全性而提供的各种功能。本系列的下一篇文章将演示此框架的基于方法的安全性,它提供了对企业数据访问的更细粒度的控制。
Acegi Security System
Acegi Security System 使用安全过滤器来提供企业应用程序的身份验证和授权服务。该框架提供了不同类型的过滤器,可以根据应用程序的需求进行配置。您将在本文后面了解到 安全过滤器的不同类型;现在,只需注意可以为如下任务配置 Acegi 安全过滤器:
- 在访问一个安全资源之前提示用户登录。
- 通过检查安全标记(如密码),对用户进行身份验证。
- 检查经过身份验证的用户是否具有访问某个安全资源的特权。
- 将成功进行身份验证和授权的用户重定向到所请求的安全资源。
- 对不具备访问安全资源特权的用户显示 Access Denied 页面。
- 在服务器上记录成功进行身份验证的用户,并在用户的客户机上设置安全 cookie。使用该 cookie 执行下一次身份验证,而无需要求用户登录。
- 将身份验证信息存储在服务器端的会话对象中,从而安全地进行对资源的后续请求。
- 在服务器端对象中构建并保存安全信息的缓存,从而优化性能。
- 当用户退出时,删除为用户安全会话而保存的服务器端对象。
- 与大量后端数据存储服务(如目录服务或关系数据库)进行通信,这些服务用于存储用户的安全信息和 ECM 的访问控制策略。
正如这个列表显示的那样,Acegi 的安全过滤器允许您执行保护企业应用程序所需的几乎任何事情。
架构和组件
对 Acegi 了解越多,使用起来就越简单。这一节介绍 Acegi 的组件;接下来您将了解该框架如何使用反转控制(IOC)和 XML 配置文件来组合组件并表示它们的依赖关系。
四大组件
Acegi Security System 由四种主要类型的组件组成:过滤器、管理器、提供者和处理程序。
- 过滤器
- 这种最高级的组件提供了常见的安全服务,如身份验证、会话处理以及注销。我将在 本文后面的部分 深入讨论过滤器。
- 管理器
- 过滤器仅是安全相关功能的高级抽象:实际上要使用管理器和提供者实现身份验证处理和注销服务。管理器管理由不同提供者提供的较低级的安全服务。
- 提供者
- 有大量的提供者可用于和不同类型的数据存储服务通信,例如目录服务、关系数据库或简单的内存中的对象。这意味着您可以将用户库和访问控制协议存储在任何一种这样的数据存储服务中,并且 Acegi 的管理器将在运行时选择合适的提供者。
- 处理程序
- 有时任务可能会被分解为多个步骤,其中每个步骤由一个特定的处理程序执行。比方说,Acegi 的 注销过滤器 使用两个处理程序来退出一个 HTTP 客户机。其中一个处理程序使用户的 HTTP 会话无效,而另一个处理程序则删除用户的 cookie。当根据应用程序需求配置 Acegi 时,多个处理程序能够提供很好的灵活性。您可以使用自己选择的处理程序来执行保护应用程序所需的步骤。
反转控制
Acegi 的组件通过彼此之间的依赖来对企业应用程序进行保护。比如,一个身份验证处理过滤器需要一个身份验证管理器选择一个合适的身份验证提供者。这就是说您必须能够表示和管理 Acegi 组件的依赖关系。
IOC 实现通常用于管理 Java 组件之间的依赖关系。IOC 提供了两个重要的特性:
- 它提供了一种语法,表示应用程序所需的组件以及这些组件如何相互依赖。
- 它保证了所需的组件在运行时是可用的。
XML 配置文件
Acegi 使用 Spring 框架(请参见 参考资料)附带的流行开源 IOC 实现来管理其组件。Spring 需要您编写一个 XML 配置文件来表示组件的依赖关系,如清单 1 所示:
清单 1. Spring 配置文件的结构
<beans>
<bean id="filterChainProxy" class="org.acegisecurity.util.FilterChainProxy">
<property name="filterInvocationDefinitionSource">
<value> value here </value>
</property>
</bean>
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFitler">
<property name="authenticationManager" ref="authManager"/>
<!-- Other properties -->
</bean>
<bean id="authManager"
class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<!-- List of providers here -->
</property>
</bean>
<!-- Other bean tags -->
</beans>
|
如您所见,Acegi 使用的 Spring XML 配置文件包含一个 <beans>
标记,它封装了一些其他的 <bean>
标记。所有的 Acegi 组件(即过滤器、管理器、提供者等)实际上都是 JavaBean。XML 配置文件中的每个 <bean>
标记都代表一个 Acegi 组件。
进一步解释 XML 配置文件
首先将注意到的是每个 <bean>
标记都包含一个 class 属性,这个属性标识组件所使用的类。<bean>
标记还具有一个 id 属性,它标识作为 Acegi 组件工作的实例(Java 对象)。
比方说,清单 1 的第一个 <bean>
标记标识了名为 filterChainProxy
的组件实例,它是名为 org.acegisecurity.util.FilterChainProxy
的类的实例。
使用 <bean>
标记的子标记来表示 bean 的依赖关系。比如,注意第一个 <bean>
标记的 <property>
子标记。<property>
子标记定义了 <bean>
标记依赖的其他 bean 或值。
所以在 清单 1 中,第一个 <bean>
标记的 <property>
子标记具有一个 name 属性和一个 <value>
子标记,分别定义了这个 bean 依赖的属性的名称和值。
同样,清单 1 中的第二个和第三个 <bean>
标记定义了一个过滤器 bean 依赖于一个管理器 bean。第二个 <bean>
标记表示过滤器 bean,而第三个 <bean>
标记表示管理器 bean。
过滤器的 <bean>
标记包含一个 <property>
子标记,该子标记具有两个属性:name
和 ref
。name
属性定义了过滤器 bean 的属性,而 ref
属性引用了管理器 bean 的实例(名称)。
下一节将展示如何在 XML 配置文件中配置 Acegi 过滤器。在本文后面的内容中,您将在一个样例 Acegi 应用程序中使用过滤器。
安全过滤器
正如我前面提到的一样,Acegi 使用安全过滤器为企业应用程序提供身份验证和授权服务。您可以根据应用程序的需要使用和配置不同类型的过滤器。这一节将介绍五种最重要的 Acegi 安全过滤器。
Session Integration Filter
Acegi 的 Session Integration Filter(SIF)通常是您将要配置的第一个过滤器。SIF 创建了一个安全上下文对象,这是一个与安全相关的信息的占位符。其他 Acegi 过滤器将安全信息保存在安全上下文中,也会使用安全上下文中可用的安全信息。
SIF 创建安全上下文并调用过滤器链中的其他过滤器。然后其他过滤器检索安全上下文并对其进行更改。比如,Authentication Processing Filter(我将稍后进行介绍)将用户信息(如用户名、密码和电子邮件地址)存储在安全上下文中。
当所有的处理程序完成处理后,SIF 检查安全上下文是否更新。如果任何一个过滤器对安全上下文做出了更改,SIF 将把更改保存到服务器端的会话对象中。如果安全上下文中没有发现任何更改,那么 SIF 将删除它。
在 XML 配置文件中对 SIF 进行了配置,如清单 2 所示:
清单 2. 配置 SIF
<bean id="httpSessionContextIntegrationFilter"
class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
|
Authentication Processing Filter
Acegi 使用 Authentication Processing Filter(APF)进行身份验证。APF 使用一个身份验证(或登录)表单,用户在其中输入用户名和密码,并触发身份验证。
APF 执行所有的后端身份验证处理任务,比如从客户机请求中提取用户名和密码,从后端用户库中读取用户参数,以及使用这些信息对用户进行身份验证。
在配置 APF 时,您必须提供如下参数:
- Authentication manager 指定了用来管理身份验证提供者的身份验证管理器。
- Filter processes URL 指定了客户在登录窗口中按下 Sign In 按钮时要访问的 URL。收到这个 URL 的请求后,Acegi 立即调用 APF。
- Default target URL 指定了成功进行身份验证和授权后呈现给用户的页面。
- Authentication failure URL 指定了身份验证失败情况下用户看到的页面。
APF 从用户的请求对象中得到用户名、密码和其他信息。它将这些信息传送给身份验证管理器。身份验证管理器使用适当的提供者从后端用户库中读取详细的用户信息(如用户名、密码、电子邮件地址和用户访问权利或特权),对用户进行身份验证,并将信息存储在一个 Authentication
对象中。
最后,APF 将 Authentication
对象保存在 SIF 之前创建的安全上下文中。存储在安全上下文中的 Authentication
对象将用于做出授权决策。
APF 的配置如清单 3 所示:
清单 3. 配置 APF
<bean id="authenticationProcessingFilter"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilter">
<property name="authenticationManager"
ref="authenticationManager" />
<property name="filterProcessesUrl"
value="/j_acegi_security_check" />
<property name="defaultTargetUrl"
value="/protected/protected1.jsp" />
<property name="authenticationFailureUrl"
value="/login.jsp?login_error=1" />
</bean>
|
可以从这段代码中看到,APF 依赖于上面讨论的这四个参数。每个参数都是作为清单 3 所示的 <property>
标记配置的。
Logout Processing Filter
Acegi 使用一个 Logout Processing Filer(LPF)管理注销处理。当客户机发来注销请求时,将使用 LPF 进行处理。它标识了来自由客户机所调用 URL 的注销请求。
LPF 的配置如清单 4 所示:
清单 4. 配置 LPF
<bean id="logoutFilter" class="org.acegisecurity.ui.logout.LogoutFilter">
<constructor-arg value="/logoutSuccess.jsp"/>
<constructor-arg>
<list>
<bean class="org.acegisecurity.ui.logout.SecurityContextLogoutHandler"/>
</list>
</constructor-arg>
</bean>
|
可以看到 LPF 在其构造方法中包含两个参数:注销成功 URL(/logoutSuccess.jsp
)和处理程序列表。注销成功 URL 用来在注销过程完成后重定向客户机。处理程序执行实际的注销过程;我在这里只配置了一个处理程序,因为只需一个处理程序就可以使 HTTP 会话变为无效。我将在本系列下一篇文章中讨论更多的处理程序。
Exception Translation Filter
Exception Translation Filter(ETF)处理身份验证和授权过程中的异常情况,比如授权失败。在这些异常情况中,ETF 将决定如何进行操作。
比如,如果一个没有进行身份验证的用户试图访问受保护的资源,ETF 将显示一个登录页面要求用户进行身份验证。类似地,在授权失败的情况下,可以配置 ETF 来呈现一个 Access Denied 页面。
ETF 的配置如清单 5 所示:
清单 5. 配置 ETF
<bean id="exceptionTranslationFilter"
class="org.acegisecurity.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint">
<bean
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/login.jsp" />
</bean>
</property>
<property name="accessDeniedHandler">
<bean class="org.acegisecurity.ui.AccessDeniedHandlerImpl">
<property name="errorPage" value="/accessDenied.jsp" />
</bean>
</property>
</bean>
|
正如清单 5 所示,ETF 包含两个参数,名为 authenticationEntryPoint
和 accessDeniedHandler
。authenticationEntryPoint
属性指定登录页面,而 accessDeniedHandler
指定 Access Denied 页面。
拦截过滤器
Acegi 的拦截过滤器 用于做出授权决策。您需要在 APF 成功执行身份验证后对拦截过滤器进行配置,以使其发挥作用。拦截器使用应用程序的访问控制策略来做出授权决定。
本系列的下一篇文章将展示如何设计访问控制策略,如何将它们托管在目录服务中,以及如何配置 Acegi 以读取您的访问控制策略。但是,目前我将继续向您展示如何使用 Acegi 配置一个简单的访问控制策略。在本文后面的部分,您将看到使用简单的访问控制策略构建一个样例应用程序。
配置简单的访问控制策略可分为两个步骤:
- 编写访问控制策略。
- 根据策略配置 Acegi 的拦截过滤器。
步骤 1. 编写简单的访问控制策略
首先看一下 清单 6,它展示了如何定义一个用户及其用户角色:
清单 6. 为用户定义简单的访问控制策略
alice=123,ROLE_HEAD_OF_ENGINEERING
|
清单 6 所示的访问控制策略定义了用户名 alice
,它的密码是 123
,角色是 ROLE_HEAD_OF_ENGINEERING
。(下一节将说明如何在文件中定义任意数量的用户及其用户角色,然后配置拦截过滤器以使用这些文件。)
步骤 2. 配置 Acegi 的拦截过滤器
拦截过滤器使用三个组件来做出授权决策,我在清单 7 中对其进行了配置:
清单 7. 配置拦截过滤器
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager" />
<property name="accessDecisionManager" ref="accessDecisionManager" />
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/protected/**=ROLE_HEAD_OF_ENGINEERING
/**=IS_AUTHENTICATED_ANONYMOUSLY
</value>
</property>
<!-- More properties of the interceptor filter -->
</bean>
|
如清单 7 所示,配置所需的三个组件是 authenticationManager
、accessDecisionManager
、objectDefinitionSource
:
authenticationManager
组件与我在介绍 Authentication Processing Filter 时讨论过的身份验证管理器相同。拦截过滤器可以在授权的过程中使用 authenticationManager
重新对客户机进行身份验证。
accessDecisionManager
组件管理授权过程,这部分内容将在本系列的下篇文章中详细讨论。
objectDefinitionSource
组件包含对应于将要发生的授权的访问控制定义。例如,清单 7 中的 objectDefinitionSource
属性值包含两个 URL(/protected/*
和 /*
)。其值定义了这些 URL 的角色。/protected/*
URL 的角色是 ROLE_HEAD_OF_ENGINEERING
。您可以根据应用程序的需要定义任何角色。
回想一下 清单 6,您为用户名 alice
定义了 ROLE_HEAD_OF_ENGINEERING
。这就是说 alice
将能够访问 /protected/*
URL。
过滤器工作原理
正如您已经了解到的一样,Acegi 的组件彼此依赖,从而对您的应用程序进行保护。在本文后面的部分,您将看到如何对 Acegi 进行配置,从而按照特定的顺序应用安全过滤器,因此需要创建过滤器链。出于这个目的,Acegi 保存了一个过滤器链对象,它封装了为保护应用程序而配置了的所有过滤器。图 1 展示了 Acegi 过滤器链的生命周期,该周期从客户机向您的应用程序发送 HTTP 请求开始。(图 1 显示了服务于浏览器客户机的容器。)
图 1. 托管 Acegi 过滤器链以安全地为浏览器客户机服务的容器
下面的步骤描述了过滤器链的生命周期:
- 浏览器客户机向您的应用程序发送 HTTP 请求。
- 容器接收到 HTTP 请求并创建一个请求对象,该对象将封装 HTTP 请求中包含的信息。容器还创建一个各种过滤器都可处理的响应对象,从而为发出请求的客户机准备好 HTTP 响应。容器然后调用 Acegi 的过滤器链代理,这是一个代理过滤器。该代理知道应用的过滤器的实际顺序。当容器调用代理时,它将向代理发送请求、响应以及过滤器链对象。
- 代理过滤器调用过滤器链中第一个过滤器,向其发送请求、响应和过滤器链对象。
- 链中的过滤器逐个执行其处理。一个过滤器可以通过调用过滤器链中下一个过滤器随时终止自身处理。有的过滤器甚至根本不执行任何处理(比如,如果 APF 发现一个到来的请求没有要求身份验证,它可能会立即终止其处理)。
- 当身份验证过滤器完成其处理时,这些过滤器将把请求和响应对象发送到应用程序中配置的拦截过滤器。
- 拦截器决定是否对发出请求的客户机进行授权,使它访问所请求的资源。
- 拦截器将控制权传输给应用程序(比如,成功进行了身份验证和授权的客户机请求的 JSP 页面)。
- 应用程序改写响应对象的内容。
- 响应对象已经准备好了,容器将响应对象转换为 HTTP 响应,并将响应发送到发出请求的客户机。
为帮助您进一步理解 Acegi 过滤器,我将详细探讨其中两个过滤器的操作:Session Integration Filter 和 Authentication Processing Filter。
SIF 如何创建一个安全上下文
图 2 展示了 SIF 创建安全上下文所涉及到的步骤:
图 2. SIF 创建安全上下文
现在详细地考虑下面这些步骤:
- Acegi 的过滤器链代理调用 SIF 并向其发送请求、响应和过滤器链对象。注意:通常将 SIF 配置为过滤器链中第一个过滤器。
- SIF 检查它是否已经对这个 Web 请求进行过处理。如果是的话,它将不再进一步进行处理,并将控制权传输给过滤器链中的下一个过滤器(参见下面的第 4 个步骤)。如果 SIF 发现这是第一次对这个 Web 请求调用 SIF,它将设置一个标记,将在下一次使用该标记,以表示曾经调用过 SIF。
- SIF 将检查是否存在一个会话对象,以及它是否包含安全上下文。它从会话对象中检索安全上下文,并将其放置在名为 security context holder 的临时占位符中。如果不存在会话对象,SIF 将创建一个新的安全上下文,并将它放到 security context holder 中。注意:security context holder 位于应用程序的范围内,所以可以被其他的安全过滤器访问。
- SIF 调用过滤器链中的下一个过滤器。
- 其他过滤器可以编辑安全上下文。
- SIF 在过滤器链完成处理后接收控制权。
- SIF 检查其他的过滤器是否在其处理过程中更改了安全上下文(比如,APF 可能将用户详细信息存储在安全上下文中)。如果是的话,它将更新会话对象中的安全上下文。就是说在过滤器链处理过程中,对安全上下文的任何更改现在都保存在会话对象中。
APF 如何对用户进行身份验证
图 3 展示了 APF 对用户进行身份验证所涉及到的步骤:
图 3. APF 对用户进行身份验证
现在仔细考虑以下这些步骤:
- 过滤器链中前面的过滤器向 APF 发送请求、响应和过滤链对象。
- APF 使用从请求对象中获得的用户名、密码以及其他信息创建身份验证标记。
- APF 将身份验证标记传递给身份验证管理器。
- 身份验证管理器可能包含一个或更多身份验证提供者。每个提供者恰好支持一种类型的身份验证。管理器检查哪一种提供者支持它从 APF 收到的身份验证标记。
- 身份验证管理器将身份验证标记发送到适合进行身份验证的提供者。
- 身份验证提供者支持从身份验证标记中提取用户名,并将它发送给名为 user cache service 的服务。Acegi 缓存了已经进行过身份验证的用户。该用户下次登录时,Acegi 可以从缓存中加载他或她的详细信息(比如用户名、密码和权限),而不是从后端数据存储中读取数据。这种方法使得性能得到了改善。
- user cache service 检查用户的详细信息是否存在于缓存中。
- user cache service 将用户的详细信息返回给身份验证提供者。如果缓存不包含用户详细信息,则返回 null。
- 身份验证提供者检查缓存服务返回的是用户的详细信息还是 null。
- 如果缓存返回 null,身份验证提供者将用户名(在步骤 6 中提取)发送给另一个名为 user details service 的服务。
- user details service 与包含用户详细信息的后端数据存储通信(如目录服务)。
- user details service 返回用户的详细信息,或者,如果找不到用户详细信息则抛出身份验证异常。
- 如果 user cache service 或者 user details service 返回有效的用户详细信息,身份验证提供者将使用 user cache service 或 user details service 返回的密码来匹配用户提供的安全标记(如密码)。如果找到一个匹配,身份验证提供者将用户的详细信息返回给身份验证管理器。否则的话,则抛出一个身份验证异常。
- 身份验证管理器将用户的详细信息返回给 APF。这样用户就成功地进行了身份验证。
- APF 将用户详细信息保存在 图 2 所示由步骤 3 创建的安全上下文中。
- APF 将控制权传输给过滤器链中的下一个过滤器。
一个简单的 Acegi 应用程序
在本文中,您已经了解了很多关于 Acegi 的知识,所以现在看一下利用您目前学到的知识能做些什么,从而结束本文。对于这个简单的演示,我设计了一个样例应用程序(参见 下载),并对 Acegi 进行了配置以保护它的一些资源。
样例应用程序包含 5 个 JSP 页面:index.jsp、protected1.jsp、protected2.jsp、login.jsp 和 accessDenied.jsp。
index.jsp 是应用程序的欢迎页面。它向用户显示了三个超链接,如图 4 所示:
图 4. 样例应用程序的欢迎页面:
图 4 所示的链接中,其中两个链接指向了被保护的资源(protected1.jsp 和 protected2.jsp),第三个链接指向登录页面(login.jsp)。只有在 Acegi 发现用户没有被授权访问受保护的资源时,才会显示 accessDenied.jsp 页面。
如果用户试图访问任何受保护的页面,样例应用程序将显示登录页面。当用户使用登录页面进入后,应用程序将自动重定向到被请求的受保护资源。
用户可以通过单击欢迎页面中的第三个链接直接请求登录页面。这种情况下,应用程序显示用户可以进入系统的登录页面。进入系统以后,应用程序将用户重定向到 protected1.jsp,它是用户进入系统而没有请求特定的受保护资源时显示的默认资源。
配置样例应用程序
为本文下载的源代码包含一个名为 acegi-config.xml 的 XML 配置文件,它包含 Acegi 过滤器的配置。根据 安全过滤器的讨论 中的示例,您应该很熟悉这些配置。
我还为样例应用程序编写了一个 web.xml
文件,如清单 8 所示:
清单 8. 样例应用程序的 web.xml 文件
<web-app>
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>/WEB-INF/acegi-config.xml</param-value>
</context-param>
<filter>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<filter-class>
org.acegisecurity.util.FilterToBeanProxy
</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>
org.acegisecurity.util.FilterChainProxy
</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi Filter Chain Proxy</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>
org.springframework.web.context.ContextLoaderListener
</listener-class>
</listener>
</web-app>
|
web.xml 文件配置如下:
acegi-config.xml
文件的 URL 位于 <context-param>
标记中。
- Acegi 过滤器链代理类的名称位于
<filter>
标记中。
- URL 到 Acegi 过滤器链代理的映射在
<filter-mapping>
标记中。注意:您可以简单地将应用程序的所有 URL(/*
)映射到 Acegi 过滤器链代理。Acegi 将对映射到 Acegi 过滤器链代理上的所有 URL 应用安全性。
- 应用程序上下文加载程序位于
<listener>
标记中,它将加载 Spring 的 IOC 框架。
部署并运行应用程序
部署并运行样例应用程序非常的简单。只需要完成两件事情:
- 将 acegisample.war 文件从本教程下载的源代码中复制到安装 Tomcat 的
webapps
目录中。
- 从 Acegi Security System 主页 下载并解压缩 acegi-security-1.0.3.zip。您将找到一个名为 acegi-security-sample-tutorial.war 的样例应用程序。解压缩 war 文件并提取其 WEB-INF/lib 文件夹中所有的 jar 文件。将所有的 JAR 文件从 WEB-INF/lib 文件夹中复制到 theacegisample.war 应用程序的 WEB-INF/lib 文件夹。
现在,您已经为运行样例应用程序做好准备了。启动 Tomcat 并将浏览器指向 http://localhost:8080/acegisample/
。
您将看到 图 4 所示的欢迎页面,但是此时显示的页面是真实的。请继续运行程序,并查看在尝试访问欢迎页面显示的不同链接时会发生什么状况。
结束语
在使用 Acegi 保护 Java 应用程序 系列的第一篇文章中,您了解了 Acegi 安全系统的特性、架构和组件,学习了大量有关 Acegi 安全过滤器的知识,这些过滤器被集成到 Acegi 的安全框架中。您还学习了如何使用 XML 配置文件配置组件依赖关系,并查看了 Acegi 的安全过滤器在样例程序中工作的情形,该应用程序可以实现基于 URL 的安全性。
本文所述的安全技术非常的简单,所以 Acegi 使用这些技术实现安全性。本系列的下一文章将开始介绍 Acegi 的一些较为高级的应用,首先是编写访问控制协议并将其存储到目录服务中。您还将了解到如何配置 Acegi,使它与目录服务交互,从而实现您的访问控制策略。
下载
描述 |
名字 |
大小 |
下载方法 |
本文源代码 |
j-acegi1.zip |
10KB |
HTTP |
参考资料
学习
获得产品和技术
讨论
关于作者
|
|
|
Bilal Siddiqui 是一名电子工程师、XML 顾问,他还是 WaxSys(主要从事电子商务简化)的创建者之一。自从 1995 年毕业于拉合尔工程技术大学(University of Engineering and Technology,Lahore)电子工程专业以后,他就开始为工业控制系统设计各种软件解决方案。稍后,他致力于 XML 方面并使用他在 C++ 编程中取得的经验来构建基于 Web 和 WAP 的 XML 处理工具、服务器端解析方案和服务应用程序。Bilal 是一名技术推广者,并且是一名多产的技术作家。
|
来自:http://www.cnblogs.com/amboyna/archive/2008/03/25/1122079.html
最近项目使用Acegi作为安全框架的实现,效果不错,就写了这篇文章作为总结.
对于任何一个完整的应用系统,完善的认证和授权机制是必不可少的。在基于SpringFramework的WEB应用中,
我们可以使用Acegi作为安全架构的实现。本文将介绍如何在基于Spring构架的Web应用中使用Acegi,并且详细介
绍如何配置和扩展Acegi框架以适应实际需要。
文章和代码下载:
http://www.blogjava.net/Files/youlq/Acegi.zip
注意:许多朋友在部署上遇到一些麻烦,所以我将可以部署的完整的war文件传上来,注意:java代码在acegi-sample.war\WEB-INF 目录下,例子需要Mysql,建库脚本在acegi-sample.war\db目录下。
acegi-sample.part1.rar
acegi-sample.part2.rar
acegi-sample.part3.rar
acegi-sample.part4.rar
附注:
有些朋友询问我如何部署文中的例子,在此再次说明一下(文章中已经有提到):
Mysql的建表脚本在db目录下
为了减小体积,已经将WEB-INF\lib下的依赖包删除,请自行下载以下包,并拷贝至WEB-INF\lib下:
spring-1.2.4.jar
acegi-security-0.8.3.jar
aopalliance-1.0.jar
c3p0-0.9.0.jar
commons-logging-1.0.4.jar
ehcache-1.1.jar
log4j-1.2.8.jar
mysql-connector-java-3.1.10-bin.jar
oro-2.0.8.jar
提示:
acegi-security-0.8.3.jar
aopalliance-1.0.jar
c3p0-0.9.0.jar
commons-logging-1.0.4.jar
ehcache-1.1.jar
log4j-1.2.8.jar
oro-2.0.8.jar
可以在acegi-security-0.8.3.zip所带的acegi-security-sample-contacts-filter.war中找到。
spring-1.2.4.jar
mysql-connector-java-3.1.10-bin.jar
要分别到springframework和mysql网站下载。
来自:http://www.blogjava.net/youlq/archive/2005/12/06/22678.html
1 Acegi官方发布版的下载和安装
开发者可以通过http://sourceforge.net/projects/acegisecurity或http://acegisecurity.org/下载到Acegi官方发布版,比如acegi-security-1.x.zip。图4-4展示了SF中Acegi项目的首页,它提供了下载Acegi(Spring Security)的入口。
图4-4 http://sourceforge.net/projects/acegisecurity首页
在单击图4-4中的下载(“Download Acegi Security System for Spring”)超链接后,开发者进而能够下载到最新的Acegi官方发布包。此时,建议开发者同时将acegi-security-1.x.zip、acegi-security-1.x-src.zip下载到本地,前者包含了Jar存档和若干Acegi使能应用,而后者仅仅包含了Acegi项目的源代码。
在下载到Acegi官方发布版后,通过解压acegi-security-1.x.zip,开发者能够浏览到如图4-5所示的类似内容。
图4-5 acegi-security-1.x.zip包含的内容
通常,大部分Acegi安全性项目仅仅需要使用到acegi-security-1.x.jar存档,这是Acegi的核心包,任何Acegi使能项目都需要使用到它。如果项目打算采用Java SE 5引入的Annotation注释支持,则还需要将acegi-security-tiger-1.x.jar添加到WEB-INF/lib中。如果开发者在使用Acegi提供的容器适配器支持,则还需要将acegi-security-catalina-1.x.jar(针对Tomcat)、acegi-security-jboss-1.x.jar(针对JBoss)、acegi-security-jetty-1.x.jar(针对Jetty)、acegi-security-resin-1.x.jar(针对Resin)等Jar存档复制到相应的位置,至于这些Jar包的具体使用,本书将在第10章详细阐述。
另外,acegi-security-sample-contacts-filter.war、acegi-security-sample-tutorial.war是两个直接可部署到Java EE容器(Tomcat容器)中的Web应用。
2 Subversion中的Acegi源码下载和安装
如今,Acegi基代码采用Subversion管理。开发者通过如图4-6所示的Web页面能够获得Subversion下载地址(http://sourceforge.net/svn/?group_id=104215)。
图4-6 获得下载Acegi基代码地址的Web页面
事实上,Acegi(Spring Security)基代码本身就是一个Eclipse Java项目,而且它的构建、管理工作采用了Maven 1.x/2.x(http://maven.apache.org/)。开发者可以借助Eclipse Subversive插件从Subversion存储源获得Acegi的最新基代码。图4-7展示了Subversive内置的SVN Repository Exploring。
图4-7 Subversive插件使用截图
一旦在下载完成Acegi(Spring Security)基代码后,开发者将能够持续监控到Acegi项目的最新情况,比如获得Acegi持续更新的基代码、Acegi官方文档,图4-8展示了相应的使用截图。
图4-8 持续更新Acegi基代码
3 有关Acegi的权威去处
其一,开发者可以去订阅acegisecurity-developer@lists.sourceforge.net邮件列表,图4-9展示了订阅这一邮件列表的入口。Acegi开发团队积极参与到这一邮件列表中,因此开发者从这一邮件列表能够获得Acegi的最新进展。
图4-9 订阅Acegi开发者邮件列表
其二,Acegi官方论坛(http://forum.springframework.org/),图4-10展示了论坛截图。
图4-10 Acegi官方论坛
开发者可以通过许多渠道获得一手的Acegi知识、开发和部署经验。
5 小结
本章围绕Acegi(Spring Security)的认证策略进行了阐述,比如基于过滤器的设计、与认证源解耦、AcegiSecurityException异常体系等。另外,我们还针对Acegi发布版和基代码的下载进行了简要介绍。
下章将深入到Acegi支持的各种认证机制中。
【参考及推荐资料】
l http://acegisecurity.org/
l http://sourceforge.net/projects/acegisecurity
l http://www.polarion.org/index.php?page=overview&project=subversive
来自:http://book.csdn.net/bookfiles/310/10031012826.shtml
如何实施内容管理解决方案呢?让我们借鉴一下如下做法。
IBM内容管理第8版(CM V8)提供了一套可靠的、易升级的、强劲的企业内容管理体系架构,它也同时提供了强劲的、安全的和高扩展能力的服务,使得企业用户能非常容易地访问电子商务的内容。这种体系架构能够通过具有强劲、灵活的数据模型的单一存储库来支持不同的、异构的内容管理技术,包括文档管理、数字媒体资产管理、网站内容管理等,这种数据模型使得我们能非常容易地在不同系统之间实现内容共享,从而提高了业务处理流程效率。
IBM CM V8也提供了各种特性来满足对内容整个生命周期的管理,包括捕获、存储、组织、流转、归档、跟踪和销毁。
IBM内容管理解决方案平台基于用户对于内容进行管理的需求而开发,并且IBM内容管理产品第8版本有了更多的新功能。
高扩展性的体系架构
CM服务器由一个索引服务器(Library Server)、一个或多个对象服务器(Object Server或称Resource Manager)和一个或多个客户程序构成,通过提供具有专利技术的三角形架构的体系结构,使得系统能依据需求随意扩展。资源管理服务器能分布在网络上的任何地方,并且通过统一的索引服务器而实现集中管理。它以坚实的技术实力为基础,融合了基于SQL标准的关系数据库。系统具有很强的扩容性,可以由单机系统扩容到SMP并行多处理器系统和SP超级并行处理计算机系统都可以适用。通过实现分布式基于对象的两阶段提交技术,确保索引数据和媒体数据的参照完整性,从而不会出现孤岛式的内容对象。
灵活、强劲的数据模型
IBM CM V8提供了灵活、强劲的数据模型,通过CM V8的数据模型,能够非常容易地实现任何复杂和多变的元数据标准。简单来说,任何用XML格式描述的元数据信息,都可以在CM中得到快速实现,并且灵活地进行改变,从而充分和容易地通过客户化来实现各种元数据标准,而不需要任何系统设计和编程工作,且实现系统性能的最优化。
高效的查询访问能力
CM提供了多种查询方法,包括参数检索、全文检索以及面向中文的中文智能检索,并且CM能支持用户同时和分开使用这几种检索方法。
通过参数检索,用户能快速访问所有元数据信息,象客户名称、客户号等。全文检索能对已建立了文本索引的内容进行自由文本检索,从而快速查找到相关的文档;而中文智能检索能力将基于中文语义进行检索,支持包括近义词、上下位概念、同一名称等智能检索。
企业范围的内容整合
企业都有许多的信息孤岛在管理着数据和数字内容,这些解决方案都有它们自己的方式来访问信息,包括语法、语义和接口都不一样。定制的应用开发需要从这些分离的数据源和专门的过滤方法来收集合访问所有的信息。CM通过提供信息整合的体系架构来实现单一入口的针对内容存储库的访问接口。
分布式存储和多级存储管理
CM的资源管理器是对象存储库,一个索引服务器能支持多个本地和远程的资源管理器,用户通过索引服务器来对对象进行存储和访问,资源管理器基于WebSphere的最新技术建立,通过标准的HTTP、FTP和FILE协议来进行通讯,支持基于Internet的应用。资源管理器和索引服务器合作来确保数据完整性和一致性,资源管理器通过数据库来记录对象的位置,同时资源管理器提供了缓存能力来提高对象访问的性能。
文档流转
CM提供了易于使用的强劲的文档流转功能,能支持以内容为基础的业务流转,文档流转是CM内置的工作流服务,包含了很高的性能、很少的脚本、整合的权限控制列表以及强劲和灵活的工作流建模能力。文档流转功能在CM客户端都得到了体现,并且用户可以非常容易地定制工作流应用。用户可以通过C++或Java,JavaBeans来实现工作流的操作和系统管理功能。
更高级的工作流
IBM CM提供了更高级的工作流功能,来实现企业范围灵活的文档流转功能,EIP高级工作流支持并行流转、数据变量和子进程,并且提供了将CM、CM Ondemand、CM ImagePlus等存储库中的内容进行集中流转的能力。
高层次的应用开发工具
CM整个系统的设计完全围绕着对象管理来进行的,所以不管是数据模型,还是权限管理以及开发接口,我们都可以通过对象管理的方法DDO/XDO来进行设置和开发,而不需要面向SQL和底层的表结构来进行开发,也不需要针对底层的存储管理API来进行开发。我们只需要通过统一的、满足DDO/XDO标准的管理模式和API来进行内容管理,包括编目、权限、存储、迁移、流程控制等等。
开放标准的全面支持
从Web环境、开发语言到网络协议、操作系统,乃至数据库和应用软件,CM全部遵循通用的国际或行业标准,如开发语言:C++、Java / Java Beans、ActiveX;传输连接标准:XML、HTTP、RTSP、JDBC;格式标准:TIFF等所有图像格式、 HTML、Office、MPEG(1、2、4)、Quicktime、AVI、MP3等;协议标准:CORBA、ODMA、LDAP、SQL和Xquery;系统平台:Sun Solaris、Windows、AIX、S/390、AS/400;其它数据源:Oracle、DB2、Microsft、Sybase和其它内容管理系统。
实施中的关键环节
就目前一般的内容管理应用来看,应该十分重视有效地采集、管理、应用各种非结构化和半结构化的资源,并且将它们与企业的其他业务系统中的各种结构化信息集成起来。
成立专门小组实施内容管理策略
企业在制定内容管理方案时,应考虑到设计、实施、质量监控的问题,人员的组成应涉及到战略、信息技术、业务等部门,同时还要有企业的主管领导参与,以使决策更加有效和迅速地执行。 还要确立总体的项目计划、实施策略、项目制度,以指导项目的进行。
梳理所有内容
在这个阶段中,要对企业中现有的内容资源进行细致的盘清,确定企业内容资源的价值、数量以及获取这些内容的难易程度。一般可以从三个方面着手:整理、清查已经公开发布过的各种文档;确定各种尚未公开发布过的各种知识资源;仔细地清点企业从外界得到的各种内容资源,以此对企业内部的内容资产有一个全面的了解和把握,这是确定内容管理系统内容和技术架构中非常重要的一个环节。
确定内外部内容需求
此间要深入研究企业内部和外部对内容的各种需求,使业务可以得到更好的支撑。对于企业内部来说,从内容管理中得到较多的是企业决策管理者、销售和市场部门、客户服务和技术支持部门、研究与开发部门的资源。对于企业外部来说,内容管理应该侧重挖掘潜在和现有的客户、机构和个人投资者,以及能够对公司产生影响的权威人士等信息。依据他们对于内容的需求,就可以制定出知识在企业业务中的需求蓝图,来描述内容、业务、人员之间的关系。
确定策略确定产品确定实施计划
满足各部门和人员的内容需求,需要从内容建设和建设内容管理系统入手。内容建设包括是否购买外部的内容,怎样将现有的内容重新整理,以满足新的需求或者满足不同人员的需求,怎样进行研发创新等;建设内容管理系统,并采取切实可行的措施,以确保应用。这样,就首先要确定内容管理系统的建设策略,然后要进行产品和技术方案的选择。在技术路线确定以后,可根据产品的特点、内容管理的要求,制定出系统的实施计划,以指导系统的建设。
建设好内容管理系统确保应用
为了确保内容管理系统的有效应用,可以事先选定一二个部门进行试点。此间,要注意多种内容获取渠道的集成,仅将内容存储好是远远不够的,用户应该能通过多种渠道、多种形式获得所需的信息;还要注意与其它系统进行有效的集成,包括与OA系统、邮件系统、ERP、CRM等业务系统的集成,同时在集成时,还要综合考虑需求的强烈程度、成本等诸多因素,按收益成本最大化的原则进行集成。
一天,一只兔子在山洞前写文章,一只狼走了过来,问:“兔子啊,你在干什么?”答曰:“写文章。”问:“什么题目?”答曰:“《浅谈兔子是怎样吃掉狼的》。”狼哈哈大笑,表示不信,于是兔子把狼领进山洞。过了一会,兔子独自走出山洞,继续写文章。一只野猪走了过来,问:“兔子你在写什么?”答:“文 章。”问:“题目是什么?”答:“《浅谈兔子是如何把野猪吃掉的》。”野猪不信,于是同样的事情发生。
最后,在山洞里,一只狮子在一堆白骨之间,满意的剔着牙读着兔子交给它的文章,题目:“《一只动物,能力大小关键要看你的老板是谁》。”
这只兔子有次不小心告诉了他的一个兔子朋友,这消息逐渐在森林中传播;狮子知道后非常生气,他告诉兔子:“如果这个星期没有食物进洞,我就吃你。”于是兔子继续在洞口写文章。一只小鹿走过来,“兔子,你在干什么啊?”“写文章”“什么题目”““《浅谈兔子是怎样吃掉狼的》”。“哈哈,这个事情全森林都知道啊,你别胡弄我了,我是不会进洞的”。“我马上要退休了,狮子说要找个人顶替我,难道你不想这篇文章的兔子变成小鹿么”。小鹿想了想,终于忍不住诱惑,跟随兔子走进洞里。过了一会,兔子独自走出山洞,继续写文章。一只小马走过来,同样是事情发生了。
最后,在山洞里,一只狮子在一堆白骨之间,满意的剔着牙读着兔子交给它的文章,题目是:《如何发展下线动物为老板提供食物》
随着时间的推移,狮子越长越大,兔子的食物已远远不能填饱肚子。一日,他告诉兔子:“我的食物量要加倍,例如:原来4天一只小鹿,现在要2天一只,如果一周之内改变不了局面我就吃你。于是,兔子离开洞口,跑进森林深处,他见到一只狼,“你相信兔子能轻松吃掉狼吗”。狼哈哈大笑,表示不信,于是兔子把狼领进山洞。过了一会,兔子独自走出山洞,继续进入森林深处。这回他碰到一只野猪----“你相信兔子能轻松吃掉野猪吗” 。野猪不信,于是同样的事情发生了。原来森林深处的动物并不知道兔子和狮子的故事。
最后,在山洞里,一只狮子在一堆白骨之间,满意的剔着牙读着兔子交给它的文章,题目是:《如何实现由坐商到行商的转型为老板提供更多的食物》
时间飞快,转眼之间,兔子在森林里的名气越来越大,因为大家都知道它有一个很历害的老板。这只小兔开始横行霸道,欺上欺下,没有动物敢惹。它时时想起和乌龟赛跑的羞辱。它找到乌龟说:“三天之内,见我老板!”扬长而去。乌龟难过的哭了,这时却碰到了一位猎人,乌龟把这事告诉了他,猎人哈哈大笑。于是森林里发生了一件重大事情,猎人披着狮子皮和乌龟一起在吃兔子火锅。
地下丢了半张纸片歪歪扭扭的写着:山外青山楼外楼,强中还有强中手啊!!
在很长一段时间里森林里恢复了往日的宁静,兔子吃狼的故事似乎快要被大家忘记了,不过一只年轻的老虎在听说了这个故事后,被激发了灵感。于是他抓住了一只羚羊,对羚羊说,如果你可以象以前的兔子那样为我带来食物那我就不吃你。于是,羚羊无奈的答应了老虎,而老虎也悠然自得的进了山洞。可是三天过去了,也没有见羚羊领一只动物进洞。他实在憋不住了,想出来看看情况。羚羊早已不在了,他异常愤怒。
正在他暴跳如雷的时候突然发现了羚羊写的一篇文章,题目是:《想要做好老板先要懂得怎样留住员工》。
1)机器思维
优秀的程序员最擅长和电脑程序打交道,并通过代码去控制反馈。而管理需要和人打交道,需要收集人的反馈。电脑是按逻辑来执行的,而人却要复杂很多,特别是团队中有女性成员,挑战难度就更大。由于长期和电脑接触,很多程序员缺乏和别人沟通的技巧,或者说情商相对较低。这在管理上是比较致命的缺点。
2)BUG思维
优秀的程序员追求完美,看自己或者别人代码时第一反应是看什么地方可能有BUG, 管理时如果带着BUG思维,就会只看到别人的不足和错误,而不去表扬其有进步的地方。(完美思维的坏处还有一个,就是过于关注细节)如果方向和前提有问题,过于关注细节反而会带来延误。
3)工匠思维
程序员靠手艺吃饭,创业总是会碰到各种困难,在碰到困境的时候程序员出身的创业者是有退路的,大不了我再回去写程序搞技术好了。创业最需要的就是坚持,需要一种永不言弃的精神气,不能坚持到底,也就不能收获果实。
4)大侠思维
以技术创业起家的容易迷信技术,忽视市场,忽视管理,总以为只有自己的是最好的。遗憾的是技术变迁实在太快,一时的先进不能代表永远的先进。先进的技术也不一定就是致胜的法宝。
5)边界思维
程序员设计代码和系统时,常常会考虑要处理边界和异常。反映到思维习惯上,就是遇到问题,就会全面的思考各种情况。这是很好的优点,但做事业时,这有时候反而会是缺点。
上面五类有不少具体例子,大家也可以看看自己的思维习惯里面是不是这样?
习惯是很难改变的,最好的处理方式是找到搭档,能弥补自己的不足,这样成功的概率才会加大。
来自:http://www.blog.edu.cn/user3/KingThie/archives/2007/1822533.shtml
在eclipse中,启动run on server时报错:
Resource is out of sync with the file system: '/Test_1_Struts_Spring_Hibernate/WebContent/WEB-INF/.struts-config.xml.strutside'.
查阅资料后发现这是文件系统不同步的问题,是因为在eclipse之外对工程中的resource进行修改引起的;但是,有时没有在eclipse之外进行修改,也会报类似的错误。
解决办法:需要手动刷新一下资源管理器。
(1)在eclipse中,工程目录右键,选择F5(refresh)
(2)设置eclipse自动刷新。
通过Window->Preferences->General->Workspace,选中Refresh automatically。
来自:
http://hi.baidu.com/proglife/blog/item/0c14d0d9ad404e2910df9b3a.html
Oracle提供了两个脚本dbstart和dbshut用来启动和关闭数据库.
这两个脚本首先读取oratab(/etc/oratab)文件来决定哪个数据库是需要自动启动和关闭,然后启动和关闭那些数据库,
oratab文件通过root.sh创建.
[oracle@chicago oracle]$ cat /etc/oratab
#
# This file is used by ORACLE utilities. It is created by root.sh
# and updated by the Database Configuration Assistant when creating
# a database.
# A colon, ':', is used as the field terminator. A new line terminates
# the entry. Lines beginning with a pound sign, '#', are comments.
#
# Entries are of the form:
# $ORACLE_SID:$ORACLE_HOME:<N|Y>:
#
# The first and second fields are the system identifier and home
# directory of the database respectively. The third filed indicates
# to the dbstart utility that the database should , "Y", or should not,
# "N", be brought up at system boot time.
#
# Multiple entries with the same $ORACLE_SID are not allowed.
#
#
orcl:/u01/app/oracle/oracle/product/10.2.0/db_1:Y
不过,dbstart/dbshut脚本中都包含有错误.
需要修改ORACLE_HOME_LISTNER=$ORACLE_HOME
[oracle@chicago oracle]$ dbstart
ORACLE_HOME_LISTNER is not SET, unable to auto-start Oracle Net Listener
Usage: /u01/app/oracle/oracle/product/10.2.0/db_1/bin/dbstart ORACLE_HOME
[oracle@chicago oracle]$ vi $ORACLE_HOME/bin/dbstart
:
#
# $Id: dbstart.sh.pp 25-may-2005.14:52:00 vikrkuma Exp $
# Copyright (c) 1991, 2005, Oracle. All rights reserved.
#
###################################
#
# usage: dbstart $ORACLE_HOME
#
# This script is used to start ORACLE from /etc/rc(.local).
# It should ONLY be executed as part of the system boot procedure.
#
# This script will start all databases listed in the oratab file
# whose third field is a "Y". If the third field is set to "Y" and
# there is no ORACLE_SID for an entry (the first field is a *),
# then this script will ignore that entry.
#
# This script requires that ASM ORACLE_SID's start with a +, and
# that non-ASM instance ORACLE_SID's do not start with a +.
#
# If ASM instances are to be started with this script, it cannot
# be used inside an rc*.d directory, and should be invoked from
# rc.local only. Otherwise, the CSS service may not be available
# yet, and this script will block init from completing the boot
# cycle.
#
# If you want dbstart to auto-start a single-instance database that uses
# an ASM server that is auto-started by CRS (this is the default behavior
# for an ASM cluster), you must change the database's ORATAB entry to use
# a third field of "W" and the ASM's ORATAB entry to use a third field of "N".
# These values specify that dbstart auto-starts the database only after
# the ASM instance is up and running.
#
# Note:
# Use ORACLE_TRACE=T for tracing this script.
#
# The progress log for each instance bringup plus Error and Warning message[s]
# are logged in file $ORACLE_HOME/startup.log. The error messages related to
# instance bringup are also logged to syslog (system log module).
# The Listener log is located at $ORACLE_HOME_LISTNER/listener.log
#
# To configure:
# 1) Set ORATAB:
# On Solaris
# ORATAB=/var/opt/oracle/oratab
# All other UNIX platforms
# ORATAB=/etc/oratab
#
# 2) Update $ORATAB/oratab with Database Instances that need to be started up.
# Entries are of the form:
# $ORACLE_SID:$ORACLE_HOME:<N|Y|W>:
# An example entry:
# main:/usr/lib/oracle/emagent_10g:Y
#
# Overall algorithm:
# 1) Bring up all ASM instances with 'Y' entry in status field in oratab entry
# 2) Bring up all Database instances with 'Y' entry in status field in
# oratab entry
# 3) If there are Database instances with 'W' entry in status field
# then
# iterate over all ASM instances (irrespective of 'Y' or 'N') AND
# wait for all of them to be started
# fi
# 4) Bring up all Database instances with 'W' entry in status field in
# oratab entry
#
#####################################
LOGMSG="logger -puser.alert -s "
trap 'exit' 1 2 3
# for script tracing
case $ORACLE_TRACE in
T) set -x ;;
esac
# Set path if path not set (if called from /etc/rc)
case $PATH in
"") PATH=/bin:/usr/bin:/etc
export PATH ;;
esac
# Save LD_LIBRARY_PATH
SAVE_LLP=$LD_LIBRARY_PATH
# First argument is used to bring up Oracle Net Listener
ORACLE_HOME_LISTNER=$ORACLE_HOME
if [ ! $ORACLE_HOME_LISTNER ] ; then
echo "ORACLE_HOME_LISTNER is not SET, unable to auto-start Oracle Net Listener"
echo "Usage: $0 ORACLE_HOME"
else
LOG=$ORACLE_HOME_LISTNER/listener.log
# Start Oracle Net Listener
if [ -x $ORACLE_HOME_LISTNER/bin/tnslsnr ] ; then
echo "$0: Starting Oracle Net Listener" >> $LOG 2>&1
"/u01/app/oracle/oracle/product/10.2.0/db_1/bin/dbstart" 461L, 13926C written
[oracle@chicago oracle]$ dbstart
Processing Database instance "orcl": log file /u01/app/oracle/oracle/product/10.2.0/db_1/startup.log
[oracle@chicago oracle]$ dbshut
ORACLE_HOME_LISTNER is not SET, unable to auto-stop Oracle Net Listener
Usage: /u01/app/oracle/oracle/product/10.2.0/db_1/bin/dbshut ORACLE_HOME
Processing Database instance "orcl": log file /u01/app/oracle/oracle/product/10.2.0/db_1/shutdown.log
[oracle@chicago oracle]$ vi $ORACLE_HOME/bin/dbshut
:
#
# $Id: dbshut.sh.pp 11-may-2005.19:37:00 vikrkuma Exp $
# Copyright (c) 1991, 2005, Oracle. All rights reserved.
#
###################################
#
# usage: dbshut $ORACLE_HOME
#
# This script is used to shutdown ORACLE from /etc/rc(.local).
# It should ONLY be executed as part of the system boot procedure.
#
# This script will shutdown all databases listed in the oratab file
# whose third field is a "Y" or "W". If the third field is set to "Y" and
# there is no ORACLE_SID for an entry (the first field is a *),
# then this script will ignore that entry.
#
# This script requires that ASM ORACLE_SID's start with a +, and
# that non-ASM instance ORACLE_SID's do not start with a +.
#
# Note:
# Use ORACLE_TRACE=T for tracing this script.
# Oracle Net Listener is also shutdown using this script.
#
# The progress log for each instance shutdown is logged in file
# $ORACLE_HOME/shutdown.log.
#
# To configure:
# 1) Set ORATAB:
# On Solaris
# ORATAB=/var/opt/oracle/oratab
# All other UNIX platforms
# ORATAB=/etc/oratab
#
# 2) Update $ORATAB/oratab with Database Instances that need to be shutdown.
# Entries are of the form:
# $ORACLE_SID:$ORACLE_HOME:<N|Y>:
# An example entry:
# main:/usr/lib/oracle/emagent_10g:Y
#
# Note:
# Use ORACLE_TRACE=T for tracing this script.
# Oracle Net Listener is NOT shutdown using this script.
#
# The progress log for each instance shutdown is logged in file
# $ORACLE_HOME/shutdown.log.
#
# To configure:
# 1) Set ORATAB:
# On Solaris
# ORATAB=/var/opt/oracle/oratab
# All other UNIX platforms
# ORATAB=/etc/oratab
#
# 2) Update $ORATAB/oratab with Database Instances that need to be shutdown.
# Entries are of the form:
# $ORACLE_SID:$ORACLE_HOME:<N|Y>:
# An example entry:
# main:/usr/lib/oracle/emagent_10g:Y
#
#####################################
trap 'exit' 1 2 3
case $ORACLE_TRACE in
T) set -x ;;
esac
# Set path if path not set (if called from /etc/rc)
case $PATH in
"") PATH=/bin:/usr/bin:/etc
export PATH ;;
esac
# Save LD_LIBRARY_PATH
SAVE_LLP=$LD_LIBRARY_PATH
# The this to bring down Oracle Net Listener
ORACLE_HOME_LISTNER=$ORACLE_HOME
if [ ! $ORACLE_HOME_LISTNER ] ; then
echo "ORACLE_HOME_LISTNER is not SET, unable to auto-stop Oracle Net Listener"
echo "Usage: $0 ORACLE_HOME"
else
LOG=$ORACLE_HOME_LISTNER/listener.log
# Stop Oracle Net Listener
if [ -f $ORACLE_HOME_LISTNER/bin/tnslsnr ] ; then
"/u01/app/oracle/oracle/product/10.2.0/db_1/bin/dbshut" 246L, 6592C written
[oracle@chicago oracle]$ dbstart
Processing Database instance "orcl": log file /u01/app/oracle/oracle/product/10.2.0/db_1/startup.log
[oracle@chicago oracle]$ dbshut
Processing Database instance "orcl": log file /u01/app/oracle/oracle/product/10.2.0/db_1/shutdown.log
[oracle@chicago oracle]$
来自:http://benbo.itpub.net/post/26034/311306