标签:des http java 使用 strong width
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 安全过滤器:
正如这个列表显示的那样,Acegi 的安全过滤器允许您执行保护企业应用程序所需的几乎任何事情。
对 Acegi 了解越多,使用起来就越简单。这一节介绍 Acegi 的组件;接下来您将了解该框架如何使用反转控制(IOC)和 XML 配置文件来组合组件并表示它们的依赖关系。
Acegi Security System 由四种主要类型的组件组成:过滤器、管理器、提供者和处理程序。
Acegi 的组件通过彼此之间的依赖来对企业应用程序进行保护。比如,一个身份验证处理过滤器需要一个身份验证管理器选择一个合适的身份验证提供者。这就是说您必须能够表示和管理 Acegi 组件的依赖关系。
IOC 实现通常用于管理 Java 组件之间的依赖关系。IOC 提供了两个重要的特性:
Acegi 使用 Spring 框架(请参见 参考资料)附带的流行开源 IOC 实现来管理其组件。Spring 需要您编写一个 XML 配置文件来表示组件的依赖关系,如清单 1 所示:
<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 安全过滤器。
Acegi 的 Session Integration Filter(SIF)通常是您将要配置的第一个过滤器。SIF 创建了一个安全上下文对象,这是一个与安全相关的信息的占位符。其他 Acegi 过滤器将安全信息保存在安全上下文中,也会使用安全上下文中可用的安全信息。
SIF 创建安全上下文并调用过滤器链中的其他过滤器。然后其他过滤器检索安全上下文并对其进行更改。比如,Authentication Processing Filter(我将稍后进行介绍)将用户信息(如用户名、密码和电子邮件地址)存储在安全上下文中。
当所有的处理程序完成处理后,SIF 检查安全上下文是否更新。如果任何一个过滤器对安全上下文做出了更改,SIF 将把更改保存到服务器端的会话对象中。如果安全上下文中没有发现任何更改,那么 SIF 将删除它。
在 XML 配置文件中对 SIF 进行了配置,如清单 2 所示:
<bean id="httpSessionContextIntegrationFilter" class="org.acegisecurity.context.HttpSessionContextIntegrationFilter"/>
Acegi 使用 Authentication Processing Filter(APF)进行身份验证。APF 使用一个身份验证(或登录)表单,用户在其中输入用户名和密码,并触发身份验证。
APF 执行所有的后端身份验证处理任务,比如从客户机请求中提取用户名和密码,从后端用户库中读取用户参数,以及使用这些信息对用户进行身份验证。
在配置 APF 时,您必须提供如下参数:
APF 从用户的请求对象中得到用户名、密码和其他信息。它将这些信息传送给身份验证管理器。身份验证管理器使用适当的提供者从后端用户库中读取详细的用户信息(如用户名、密码、电子邮件地址和用户访问权利或特权),对用户进行身份验证,并将信息存储在一个Authentication
对象中。
最后,APF 将 Authentication
对象保存在 SIF 之前创建的安全上下文中。存储在安全上下文中的 Authentication
对象将用于做出授权决策。
APF 的配置如清单 3 所示:
<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>
标记配置的。
Acegi 使用一个 Logout Processing Filer(LPF)管理注销处理。当客户机发来注销请求时,将使用 LPF 进行处理。它标识了来自由客户机所调用 URL 的注销请求。
LPF 的配置如清单 4 所示:
<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(ETF)处理身份验证和授权过程中的异常情况,比如授权失败。在这些异常情况中,ETF 将决定如何进行操作。
比如,如果一个没有进行身份验证的用户试图访问受保护的资源,ETF 将显示一个登录页面要求用户进行身份验证。类似地,在授权失败的情况下,可以配置 ETF 来呈现一个 Access Denied 页面。
ETF 的配置如清单 5 所示:
<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 配置一个简单的访问控制策略。在本文后面的部分,您将看到使用简单的访问控制策略构建一个样例应用程序。
配置简单的访问控制策略可分为两个步骤:
步骤 1. 编写简单的访问控制策略
首先看一下 清单 6,它展示了如何定义一个用户及其用户角色:
alice=123,ROLE_HEAD_OF_ENGINEERING
清单 6 所示的访问控制策略定义了用户名 alice
,它的密码是 123
,角色是 ROLE_HEAD_OF_ENGINEERING
。(下一节将说明如何在文件中定义任意数量的用户及其用户角色,然后配置拦截过滤器以使用这些文件。)
步骤 2. 配置 Acegi 的拦截过滤器
拦截过滤器使用三个组件来做出授权决策,我在清单 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
。您可以根据应用程序的需要定义任何角色。alice
定义了 ROLE_HEAD_OF_ENGINEERING
。这就是说 alice
将能够访问 /protected/*
URL。正如您已经了解到的一样,Acegi 的组件彼此依赖,从而对您的应用程序进行保护。在本文后面的部分,您将看到如何对 Acegi 进行配置,从而按照特定的顺序应用安全过滤器,因此需要创建过滤器链。出于这个目的,Acegi 保存了一个过滤器链对象,它封装了为保护应用程序而配置了的所有过滤器。图 1 展示了 Acegi 过滤器链的生命周期,该周期从客户机向您的应用程序发送 HTTP 请求开始。(图 1 显示了服务于浏览器客户机的容器。)
下面的步骤描述了过滤器链的生命周期:
为帮助您进一步理解 Acegi 过滤器,我将详细探讨其中两个过滤器的操作:Session Integration Filter 和 Authentication Processing Filter。
图 2 展示了 SIF 创建安全上下文所涉及到的步骤:
现在详细地考虑下面这些步骤:
图 3 展示了 APF 对用户进行身份验证所涉及到的步骤:
现在仔细考虑以下这些步骤:
在本文中,您已经了解了很多关于 Acegi 的知识,所以现在看一下利用您目前学到的知识能做些什么,从而结束本文。对于这个简单的演示,我设计了一个样例应用程序(参见 下载),并对 Acegi 进行了配置以保护它的一些资源。
样例应用程序包含 5 个 JSP 页面:index.jsp、protected1.jsp、protected2.jsp、login.jsp 和 accessDenied.jsp。
index.jsp 是应用程序的欢迎页面。它向用户显示了三个超链接,如图 4 所示:
图 4 所示的链接中,其中两个链接指向了被保护的资源(protected1.jsp 和 protected2.jsp),第三个链接指向登录页面(login.jsp)。只有在 Acegi 发现用户没有被授权访问受保护的资源时,才会显示 accessDenied.jsp 页面。
如果用户试图访问任何受保护的页面,样例应用程序将显示登录页面。当用户使用登录页面进入后,应用程序将自动重定向到被请求的受保护资源。
用户可以通过单击欢迎页面中的第三个链接直接请求登录页面。这种情况下,应用程序显示用户可以进入系统的登录页面。进入系统以后,应用程序将用户重定向到 protected1.jsp,它是用户进入系统而没有请求特定的受保护资源时显示的默认资源。
为本文下载的源代码包含一个名为 acegi-config.xml 的 XML 配置文件,它包含 Acegi 过滤器的配置。根据 安全过滤器的讨论 中的示例,您应该很熟悉这些配置。
我还为样例应用程序编写了一个 web.xml
文件,如清单 8 所示:
<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>
标记中。<filter>
标记中。<filter-mapping>
标记中。注意:您可以简单地将应用程序的所有 URL(/*
)映射到 Acegi 过滤器链代理。Acegi 将对映射到 Acegi 过滤器链代理上的所有 URL 应用安全性。<listener>
标记中,它将加载 Spring 的 IOC 框架。部署并运行样例应用程序非常的简单。只需要完成两件事情:
webapps
目录中。现在,您已经为运行样例应用程序做好准备了。启动 Tomcat 并将浏览器指向 http://localhost:8080/acegisample/
。
您将看到 图 4 所示的欢迎页面,但是此时显示的页面是真实的。请继续运行程序,并查看在尝试访问欢迎页面显示的不同链接时会发生什么状况。
在使用 Acegi 保护 Java 应用程序 系列的第一篇文章中,您了解了 Acegi 安全系统的特性、架构和组件,学习了大量有关 Acegi 安全过滤器的知识,这些过滤器被集成到 Acegi 的安全框架中。您还学习了如何使用 XML 配置文件配置组件依赖关系,并查看了 Acegi 的安全过滤器在样例程序中工作的情形,该应用程序可以实现基于 URL 的安全性。
本文所述的安全技术非常的简单,所以 Acegi 使用这些技术实现安全性。本系列的下一文章将开始介绍 Acegi 的一些较为高级的应用,首先是编写访问控制协议并将其存储到目录服务中。您还将了解到如何配置 Acegi,使它与目录服务交互,从而实现您的访问控制策略。
这期共分三部分的系列文章介绍了如何使用 Acegi 安全系统保护 Java 企业应用程序。在 本系列第一篇文章 中,我介绍了 Acegi 并解释了如何使用安全过滤器实现一个简单的基于 URL 的安全系统。在第二篇文章中,我将讨论 Acegi 的更加高级的应用,首先我将编写一个访问控制策略并将其存储在 ApacheDS 中,ApacheDS 是一个开源的 LDAP 目录服务器。我还将展示配置 Acegi 的方法,使它能够与目录服务器交互并实现您的访问控制策略。本文的结尾提供了一个示例应用程序,它使用 ApacheDS 和 Acegi 实现了一个安全的访问控制策略。
实现访问控制策略通常包含两个步骤:
Acegi 将减轻代码编写的工作,因此在这篇文章中,我将展示如何将用户和用户角色信息存储到 ApacheDS 中,然后实现这些信息的访问控制策略。在该系列的最后一篇文章中,我将展示如何配置 Acegi,实现对 Java 类的安全访问。
您可以在本文的任何位置 下载样例应用程序。参见 参考资料 下载 Acegi、Tomcat 和 ApacheDS,您需要使用它们运行样例代码和示例应用程序。
轻量级目录访问协议(Lightweight Directory Access Protocol,LDAP)可能是最流行的一种定义数据格式的协议,它针对常见的目录操作,例如对存储在目录服务器中的信息执行的读取、编辑、搜索和删除操作。本节将简要解释为什么目录服务器是属性文件存储安全信息的首选,并展示如何在 LDAP 目录中组织和托管用户信息。
本系列第一部分向您介绍了一种简单的方法,可以将用户信息以属性文件的形式保存起来(参见 第 1 部分,清单 6)。属性文件以文本格式保存用户名、密码和用户角色。对于大多数真实应用程序而言,使用属性文件存储安全信息远远不够。各种各样的理由表明,目录服务器通常都是更好的选择。其中一个原因是,真实的企业应用程序可以被大量用户访问 —— 通常是几千名用户,如果应用程序将其部分功能公开给用户和供应商时更是如此。频繁搜索文本文件中随意存储的信息,这样做的效率并不高,但是目录服务器对这类搜索进行了优化。
第 1 部分的清单 6 中的属性文件演示了另一个原因,该文件组合了用户和角色。在真实的访问控制应用程序中,您通常都需要分别定义和维护用户和角色信息,这样做可以简化用户库的维护。目录服务器为更改或更新用户信息提供了极大的灵活性,例如,反映职位升迁或新聘用人员。参见 参考资料 以了解更多关于目录服务器的使用及其优点的信息。
如果希望将用户信息存储在一个 LDAP 目录中,您需要理解一些有关目录设置的内容。本文并没有提供对 LDAP 的完整介绍(参见 参考资料),而是介绍了一些在尝试结合使用 Acegi 和 LDAP 目录之前需要了解的基本概念。
LDAP 目录以节点树的形式存储信息,如图 1 所示:
在图 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 中根节点的 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 目录中的单个节点可以使用若干个对象类:本文的示例使用了以下几个对象类:
dc
属性是强制性的。manufacturingEnterprise
。departments
节点及其子节点。member
属性,该属性包含一个用户列表。图 1 中所有的组节点(例如 engineers
节点)使用 member
属性指定该组的成员。而且,示例使用 groupOfNames
对象类的 ou
(组织单元)属性指定组用户的业务角色。alice
节点)。在真实的应用程序中,通常将有关系统用户的大量信息托管在一个 LDAP 目录中。例如,将存储每个用户的用户名、密码、职称、联系方式和工资信息。为简单起见,下面的例子将只向您展示如何保存用户名和密码。
如前所述,示例使用 ApacheDS(一种开源的 LDAP 目录服务器)演示了 Acegi 是如何使用 LDAP 目录的。示例还使用了一个开源的 LDAP 客户机(名为 JXplorer)执行简单的目录操作,例如将信息托管在 ApacheDS 上。参见 参考资料 以下载 ApacheDS、JXplorer 并了解更多有关两者协作的信息。
要创建 图 1 所示的节点树,必须首先在 ApacheDS 中创建一个根节点 org
。ApacheDS 为此提供了一个 XML 配置文件。XML 配置文件定义了一组可进行配置的 bean,从而根据应用程序的需求定制目录服务器的行为。本文只解释创建根节点所需的配置。
可以在 ApacheDS 安装中的 conf
文件夹找到名为 server.xml
的 XML 配置文件。打开文件后,会发现很多 bean 配置类似于 Acegi 的过滤器配置。查找名为 examplePartitionsConfiguration
的 bean。该 bean 控制 ApacheDS 上的分区。当创建新的根节点时,实际上将在 LDAP 目录上创建一个新的分区。
编辑 examplePartitionConfiguration
bean 以创建 org
根节点,如清单 1 所示:
<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 是如何显示该根节点的:
设置 LDAP 服务器的下一步是使用用户和组信息填充服务器。您可以使用 JXplorer 在 ApacheDS 中逐个创建节点,但是使用 LDAP Data Interchange Format (LDIF) 填充服务器会更加方便。LDIF 是可被大多数 LDAP 实现识别的常见格式。developerWorks 文章很好地介绍了 LDIF 文件的内容,因此本文将不再做详细说明。(参见 参考资料 中有关 LDIF 的详细资料。)
您可以在 源代码下载 部分查看 LDIF 文件,它表示 图 1 所示的用户和部门。您可以使用 JXplorer 将 LDIF 文件导入到 ApacheDS。要导入 LDIF 文件,在 JXplorer 中使用 LDIF
菜单,如图 4 所示:
将 LDIF 文件导入到 ApacheDS 之后,JXplorer 将显示用户节点和部门节点树,如 图 1 所示。现在您可以开始配置 Acegi,使其能够与您的 LDAP 服务器通信。
回想一下第 1 部分,其中 Acegi 使用身份验证处理过滤器(Authentication Processing Filter,APF)进行身份验证。APF 执行所有后端身份验证处理任务,例如从客户机请求中提取用户名和密码,从后端用户库读取用户参数,以及使用这些信息对用户进行身份验证。
您在第 1 部分中为属性文件实现配置了 APF,现在您已将用户库存储在 LDAP 目录中,因此必须使用不同的方式配置过滤器来和 LDAP 目录进行通信。首先看一下清单 2,它展示了在第 1 部分中的 “Authentication Processing Filter” 一节中如何为属性文件实现配置 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 服务器的通信:
<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 目录的通信。您必须对其进行配置,下一节内容将讨论该主题。
清单 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 所示:
<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
节点的子节点中查找用户。
除配置 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 来处理特定用户:
<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
bean 将读取已经通过 authenticator
bean 身份验证的用户的业务角色。清单 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 中配置初始上下文的过程:
<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 在内部使用该类构造其他处理目录操作(例如在整个目录中搜索)的类的对象。当配置初始上下文工厂时,必须指定以下内容:
search
)都将在根节点定义的子树中执行。managerDn
和 managerPassword
属性。在执行任何搜索操作之前,Acegi 必须使用目录服务器对 DN 和密码进行身份验证。您已经了解了如何将用户库托管在 LDAP 目录中,以及如何配置 Acegi 来使用来自 LDAP 目录的信息对用户进行身份验证。下一节将进一步介绍 Acegi 的身份验证处理过滤器,了解新配置的 bean 是如何管理身份验证过程的。
APF 配置完成后,将能够与 LDAP 目录进行通信来对用户进行身份验证。如果您阅读过第 1 部分,那么对与目录通信过程中 APF 执行的一些步骤不会感到陌生,我在第 1 部分中向您展示了过滤器如何使用不同的服务进行用户身份验证。图 5 所示的序列表与您在 第 1 部分图 3 看到的非常类似:
无论 APF 使用属性文件进行内部的身份验证还是与 LDAP 服务器进行通信,步骤 1 到步骤 9 与第 1 部分是相同的。这里简单描述了前 9 个步骤,您可以从步骤 10 开始继续学习特定于 LDAP 的事件:
authenticator
bean。authenticator
将使用在 清单 5 的 userDnPatterns
属性中配置的 DN 模式创建用户 DN。通过从一个 DN 模式中创建一个 DN,然后将该 DN 和用户密码(从用户请求中获得)发送到 LDAP 目录,它将逐一尝试所有可用的 DN 模式。LDAP 目录将检查该 DN 是否存在以及密码是否正确。如果其中任何一个 DN 模式可行的话,用户被绑定到 LDAP 目录中,authenticator
将继续执行步骤 15。authenticator
根据 清单 6配置的搜索查询在 LDAP 目录中搜索用户。如果 LDAP 目录没有找到用户,那么身份验证以失败告终。authenticator
。authenticator
将用户 DN 和密码发送到 LDAP 目录来检查用户密码是否正确。如果 LDAP 目录发现用户密码是正确的,该用户将被绑定到 LDAP 目录。authenticator
将用户信息发送回 LDAP 身份验证提供者。populator
bean。populator
搜索用户所属的组。populator
。populator
将用户角色信息返回给 LDAP 身份验证提供者。不论使用何种身份验证方法,最后三个步骤是相同的(步骤21、21 和 23)。
您已经了解了 APF 对用户进行身份验证的步骤。接下来是查看成功进行身份验证的用户是否被授权访问所请求的资源。这项任务由 Acegi 的拦截过滤器(Interceptor Filter,IF)完成。本节将向您展示如何配置 IF 来实现访问控制策略。
回想一下在 第 1 部分的清单 7 中配置 IF 的步骤。拦截过滤器在资源和角色之间建立映射,就是说只有具备必要角色的用户才能访问给定资源。为了演示制造业企业中不同部门的业务角色,清单 9 向现有的 IF 配置添加了另外的角色:
<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 所示:
<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 所示。现在看一下每种投票者的逻辑:
objectDefinitionSource
对象的行中找到以 ROLE_
前缀开头的角色时才进行投票。如果 RoleVoter
没有找到这样的行,将放弃投票;如果在用户业务角色中找到一个匹配的角色,它将投票给允许访问;如果没有找到匹配的角色,则投票给拒绝访问。在 清单 9 中,有两个角色具有 ROLE_
前缀:ROLE_HEAD_OF_ENGINEERING
和 ROLE_HEAD_OF_MARKETING
。objectDefinitionSource
对象中找到具有某个预定义角色的行时才进行投票。在 清单 9 中,有这样一行:IS_AUTHENTICATED_ANONYMOUSLY
。匿名身份验证意味着用户不能够进行身份验证。找到该行后,AuthenticatedVoter
将检查一个匿名身份验证的用户是否可以访问某些不受保护的资源(即这些资源没有包含在具备 ROLE_
前缀的行中)。如果 AuthenticatedVoter
发现所请求的资源是不受保护的并且 objectDefinitionSource
对象允许匿名身份验证的用户访问不受保护的资源,它将投票给允许访问;否则就投票给拒绝访问。本文提供了一个示例应用程序,它将演示您目前掌握的 LDAP 和 Acegi 概念。LDAP-Acegi 应用程序将显示一个索引页面,该页面将设计和销售文档呈现给合适的经过身份验证的用户。正如您将看到的一样,LDAP-Acegi 应用程序允许用户 alice
查看设计文档,并允许用户 bob
查看销售文档。它还允许特定用户同时查看设计和销售文档。所有这些内容都是在本文开头配置 LDAP 目录服务器时设置的。立即 下载示例应用程序来开始使用它。
在本文中,您了解了如何将用户和业务角色信息托管在 LDAP 目录中。您还详细了解了配置 Acegi 的方法,从而与 LDAP 目录交互实现访问控制策略。在本系列最后一期文章中,我将展示如何配置 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 部分 中的讨论和示例。因此,在继续阅读本文之前,在其他浏览器窗口中打开前两期文章将有助于理解本文内容。
您可能还记得,我曾在本系列的开头部分简单介绍了 企业应用程序安全性。在那次讨论中我曾提到过一种场景,其中 URL 安全性并不能完全满足这种场景的安全需求:
假设有这样一个 PDF 文档,其中包含了某制造业公司生产的特定产品的数据。文档的一部分包含了设计数据,将由公司设计部分进行编辑和更新。文档另一部分包含生产经理将使用到的生产数据。对于此类场景,需要实现更加细粒度的安全性,对文档的不同部分应用不同的访问权限。
在继续阅读之前,请考虑更多的应用程序场景,除了实现 URL 安全性以外,这些场景还要求您对单独的类访问进行保护。
业务自动化应用程序中的工作流由多个流程组成。例如,病理学实验室中执行血液测试的工作流由若干个步骤组成,其中每个步骤可看作一个业务流程:
很明显,每个流程分别由单独的授权用户执行。未授权的用户则无权执行流程。例如,实验室研究人员只负责准备试验结果,而无权编写测试报告。
几乎所有的业务自动化应用程序都普遍使用授权的业务流程。通常,每个业务流程被实现为一个 Java 类,并且需要使用合适的访问控制策略对所有类实施保护。
Business-to-business (B2B) 集成指一种常见的场景,其中的两个企业实体需要彼此公开各自的特定功能。例如,宾馆可能向旅游公司公开其房间预订功能,而后者使用该功能为游客预订空闲的房间。作为合作伙伴的旅游公司可能具有一个特定的订房率。在这个场景中,宾馆的订房系统必须先对旅游公司进行身份验证,然后才能允许他们访问所选择的类,以便按照特定的订房率进行房间预订。
现在您已经了解了对 Java 类示例的访问进行保护的重要性。在介绍能够实现更高级安全性的 Acegi 新功能之前,我将引导您回顾 Spring 框架的几个关键特性,您需要了解这些内容才能继续后文的示例。
首先对一些 Java 类进行配置并执行实例化。第 1 部分 曾介绍过,Java 类在 Spring 的 XML 配置文件中进行配置。在 Spring 配置文件中配置 Java 类的过程与 Acegi 过滤器的配置过程完全相同,因此这里不多做介绍。相反,我们将查看清单 1,它展示了名为 publicCatalog
的 bean 的配置:
<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 也对此进行了描述:
现在我们将详细讨论这些步骤:
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 类实例的重要性。
上一节讨论了 Spring 的 IOC 框架对 Java 对象进行实例化。要保护对 Java 对象的访问,Spring 的 IOC 框架使用了 bean 代理 的概念。本节首先介绍如何配置 bean 代理,然后演示 Spring 的 IOC 框架如何创建代理对象。
如果希望创建 bean 代理,Spring IOC 框架要求您对代理创建器 bean 的实例进行配置。Spring 的 IOC 框架使用代理创建器创建代理对象。清单 2 为代理创建器 bean 的配置文件,用于保护名为 privateCatalog
的 Java 对象:
<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 创建 Java 对象” 的步骤 5 和步骤 6 中,您了解了 XMLWebApplicationContext
类如何从 XML 配置文件中读取 bean 定义并随后创建 bean 实例。在创建 bean 实例之前,XMLWebApplicationContext
类将检查 XML 配置文件是否包含任何代理创建器 bean(即实现BeanPostProcessor
接口的 bean)配置。如果存在该 bean,它将要求代理创建器为您希望进行保护的 bean 创建 bean 代理。
现在考虑代理创建器如何在内部创建代理对象:
BeanNameAutoProxyCreator
类)装载 清单 2 中配置的 beanNames
属性文件中指定的所有 bean 名称。class
属性。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 中的同一个数组中。
在前面的几节中,您了解了 Spring 如何创建公有 bean 和私有 bean。出于本文的目的,您可将公有 bean 视为使用代理保护的不安全的私有 bean。现在我们来看一下客户机应用程序为访问公有 bean 和私有 bean 而必须遵循的一系列步骤。
清单 3 展示了 publicCatalog
和 privateCatalog
两个 bean 的 XML 配置。publicCatalog
bean 意味着公共访问,因此不需要使用 bean 代理。privateCatalog
bean 意味着只能由指定用户访问,因此必须加以保护。我在清单 3 中包含了 privateCatalog
bean 的 bean 代理配置:
<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 中。
//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 中的步骤:
XMLWebApplicationContext
对象。XMLWebApplicationContext
对象包含对 XML 配置文件配置的所有 Java beans 的引用。XMLWebApplicationContext
对象的引用。XMLWebApplicationContext
类公开了一个 getBean()
方法,它包含 bean 的名称并在数组中查找 “使用 Spring 创建 Java 对象” 步骤 7 中准备的 bean。在本例中,该 bean 为 publicCatalog
(未执行过代理),因此XMLWebApplicationContext
将返回实际的 bean。publicCatalog
bean 的任何方法。例如,清单 4 显示的 getData()
方法调用的执行没有应用任何访问控制并向应用程序返回类别数据。getBean()
方法尝试取回安全 bean 时,您将获得安全对象的代理而不是实际的对象。该代理就是我在 “Spring IOC 发挥效用” 步骤 4 中解释的由 Spring 框架创建的同一个对象。您现在应该对 Spring 框架如何创建 Java 对象以及客户机应用程序如何与之交互有了清晰的了解。了解了这些内容后,就更加容易理解并利用 Acegi 的方法安全性拦截器,下一节将具体介绍该主题。
只要应用程序试图访问由 Acegi 安全系统保护的 bean 方法,请求将被自动传递到 Acegi 的方法安全性拦截器。方法安全性拦截器的作用就是控制对安全 Java bean 的方法的访问。拦截器使用 Acegi 的身份验证和授权框架确认用户是否具有权利调用安全 Java bean 的方法,然后相应地作出响应。
清单 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 应用程序,它将实现您目前所了解的所有概念。
本文的 下载源代码 包含了一个名为 AcegiMethodSecurity 的示例应用程序,可按照以下方法进行配置和部署:
alice
、bob
和specialUser
)。启动 Tomcat 并尝试运行示例应用程序。
通过从浏览器访问 http://localhost:8080/acegiMethodSecurity URL 可调用示例应用程序。AcegiMethodSecurity 显示的索引页面包含两个链接(Catalog 和 Login),如图 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 就不能成功访问远程服务。
Acegi 框架提供了一种名为 run-as-replacement 的简单机制,允许您仅在方法调用期间为通过身份验证的用户配置一个或多个额外角色。您可以使用 run-as-replacement 机制为访问远程应用程序的安全 bean 配置额外角色。这意味着只要安全 bean 需要访问远程应用程序,Acegi 将为用户装载额外角色,从而允许安全 bean 访问远程应用程序。
清单 6 对 清单 5 中的方法安全性拦截器的配置进行了增强。增强后的配置使用了 run-as-replacement 机制。
清单 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.ibm.com/developerworks/cn/java/j-acegi1/index.html
http://www.ibm.com/developerworks/cn/java/j-acegi2/
http://www.ibm.com/developerworks/cn/java/j-acegi3/
使用 Acegi 保护 Java 应用程序,布布扣,bubuko.com
标签:des http java 使用 strong width
原文地址:http://www.cnblogs.com/davidwang456/p/3818846.html