本文档是"Spring框架下Acegi安全系统"(Acegi Security System for Spring)的一份参考指南,Acegi安全系统是由一序列类构成,这些类为Spring框架提供认证和授权服务。
我得感谢这份参考在Spring框架中用DocBook配置进行了精心的包装。Spring小组在他们的DocBook中对Chris Bauer (Hibernate)的帮助表示感谢。
第一章 安全
1.1 准备
为了安全使用Acegi,Acegi的每一个正式发布的JAR都署上了一个项目前导字符.这不表示对受权书中的免责声明有任何的改变,但它确保你能使用一个适当的经过检查的正式构建的Acegi安全系统.有关怎样验证JAR文件是否正确地签署以及哪些证书用来对他们进行认证的说明请参考在发布版本中根目录下的readme.txt文件.
1.2介绍
Acegi通过对流行的WEB容器的可选集成而为使用Spring编写的项目提供认证与授权的能力.这种安全架构是全部用"Spring方式"开发,包括使用bean contexts、拦截器和接口驱动的编程方式。结果,对那些为基于Spring应用的寻求安全保证应用来说,Acegi是一个非常有用的外置式的安全架构,它很容易适用于各种复机的用户化需求。
安全系统保括两个显而易见的方面:认证和授权。前者解决调用者是否是他们所声称的人的问题,而授权则是关于已经经过认证的调用者是否允许进行给定操作的问题。
在Acegi中,需要认证的用户、系统或都代理都作为一个"参与者"来题及。这个安全架构没有你在其它安全系统是所熟悉的角色或组的概念,即使Acegi完全提供相应的功能。
1.2.1 发布号
理解Acegi的发布号的含意是有用的,因为它将有助于证实你已经(或者还没将它)升级到这个项目的未来版本的。正式地,我们使用"Apache Portable Runtime Project"版本号使作指南,你可以在http://apr.apache.org/versioning.html中看到。为方便起见,我们引用了那页中的介绍。
"版本号使用标准的三个一组的整数来表示:主版本号.次版本号.补丁号。主版本号表示API不兼容和对它进行了大型升级。次版本号表示与之前的旧的较小的版本在源代码和二进制代码上保持兼容性。补丁级版本号的改变表示无论向前向后完全兼容。
1.3 系统设计
1.3.1 关键组件
大多数企业级的应用有四个基本安全需求。首先,他们要能够认证一个访问者;其次,他们需要有能力对web请求提供安全保证;第三,企业级应用需要有能力对服务层方法提供安全保证;最后,企业级应用经常要有能力对域对象实例提供安全保护。Acegi提供了一个通用的框架,来满足所有这四个通用的企业级应用安全需求。
Acegi基本上由八个关键功能部分组成。
Authentication对象 |
它包括参与者、认证证书和赋予参与者的权限。这个对象也能存储一些与认证请求有关的附加信息。例如源TCP/IP地址. |
SecurityContextHolder对象 |
它包含一个在线程级对象内的Authentication 认证对象 |
AuthenticationManager对象 |
它鉴别由ContextHolder引进的Authentication对象. |
AccessDecisionManager对象 |
授权一个给定的操作。 |
RunAsManager对象 |
当一个给定的操作被执行时,任意替代Authentication 对象 |
"安全对象"拦截器 |
配合认证、授权、run-as替换,从而调用处理执行给定的操作。 |
AfterInvocationManager对象 |
能够修改一个从"安全对象"调用中返的对象。例如删去一些参与者没有授权存取的集合元素。 |
存取控制列表(ACL)管理包 |
用于获取应用于域对象实例ACLs。 |
"安全对象"拦截器执行Acegi安全系统中的大多数关键类。这样做是为了利用框架的主要特性。
由于它的重要性,图1表示了AbstractSecurityInterceptor 拦截器类的主要关系和具体实现。
500)this.width=500" align=absMiddle border=0>
每个"安全对象"拦截器(下文中称为"保护性拦截器")作用于一个特殊类型的"安全对象"。那么,什么是安全对象?安全对象是指对它采取了安全保护措施的任何类型的对象。一个安全对象必须提供某种形式回调,这样当需要的时候保护性拦截器就能明显地起作用。保护性拦截器回调对象同时继续所请求的操作。如果安全对象不能提供一个本地的回调方法,就要写一个包装器来实现它。
每个安全对象都有一个实现它的包放在org.acegisecurity.intercept包中。在安全系统中的每个其它包是一个独立的安全对象.因此它能支持上述的任何一种安全对象.
只有那些想对拦截和认证请求采用全新方法的开发者才需要直接使用安全对象.例如有可能建立一个新的安全对象来对没有使用 MethodInvocations 对象的消息系统提供安全保护.大多数的Spring应用将简单地完全透明地使用三种普遍支持安全对象类型 ((AOP Alliance MethodInvocation, AspectJ 连接点 和 web 请求过滤拦截器)
Acegi安全系统中八个关键部分的每个部分都将在本文中详细讨论.
1.3.2 所支持的安全对象
如图1所示,目前Acegi安全系统支持三种安全对象.
第一种对象处理AOP Alliance MethodInvocation.这种安全对象用于保护Spring Bean.开发者通常用它来保护他们的业务对象.为了生成一个标准的Spring-hosted类型的bean 用作MethodInvocation,Bean简单地通过 ProxyFactoryBean 或者 BeanNameAutoProxyCreator 或者 DefaultAdvisorAutoProxyCreator来公布.大多数Spring开发者对此都已很熟悉,因为他们使用过Spring的事务处理和Spring的其它方面.
第二种对象是AspectJ连接点对象.AspectJ在域对象实例安全方面有一种特殊的应用,这通常是在 Spring bean容器之外.通过使用AspectJ,利用标准的构造函数,如new Person(),Acegi 安全系统对他们提供安全保护. AspectJSecurityInterceptor仍然由Spring管理.它创建Aspect单例,并通过适当的认证管理器、存取判别管理器等将它联系起来。
第三种是过滤器调用(FilterInvocation)对象.它包含在Acegi安全系统内。它由一个包含的过滤器创建并简单地封装了HTTP ServletRequest、ServletResponse和FilterChain 。过滤器调用对象对HTTP资源提供了安全保护。开发者通常不需要了解它的工作机制,因为他们只要在他们的web.xml中添加过滤器来让安全系统工作。
1.3.3配置属性
每一个安全对象都能作用于一些特殊的请求。例如,MethodInvocation能用于对带有任意参数的任何方的的调用。而FilterInvocation能用在任何的HTTP URL上。
Acegi 安全系统需要记录一些配置信息,这些配置信息用于各种可能的请求。例如BankManager.getBalance (int accountNumber)请求所需的安全配置与BankManager.approveLoan (int applicationNumber)请求所需的安全配置有很大的不同。与此类似,http://some.bank.com/index.htm请求所需的安全配置与http://some.bank.com/manage/timesheet.jsp请求所需的安全配置也有很大的不同。
为了存储针对各种不同请求的各种安全配置,就要使用配置属性。在实现上配置属性通过ConfigAttribute接口来表示。SecurityConfig是ConfigAttribute接口的一个具体实现,它简单地把配置属性当作一个串(String)来存储。
与特定请求相联系的ConfigAttributes 集合保存在ConfigAttributeDefinition中。这个类只是一个简单的ConfigAttributes存储器,并不做其它特别的处理。
当安全性拦截器收到一个请求时,它需要判断使用哪些配置属性。换句话说,它需要找到用于这种请求的 ConfigAttributeDefinition.这种判别是通过ObjectDefinitionSource接口来处理的.这个接口提供的主要方法是 public ConfigAttributeDefinition getAttributes(Object object),其中的Object 就是要保护的安全对象。因为安全对象包含了请求的细节,所以ObjectDefinitionSource实现类将能够抽取它所需要的细节来检查相关的 ConfigAttributeDefinition.
1.4 Request Contexts
1.4.1以前的方式
Acegi 安全系统在发布0.9.0以前,使用ContextHolder来存储session间的上下文信息(Context).Context的一个特殊子类, SecureContext定义了一个接口来存储认证对象(Authentication object).ContextHolder是一个线程 (ThreadLocal)类型的对象,有关Acegi安全系统中线程类对象用法的更全面的讨论将放在本文的后续章节中.为了保持一致,经过与其它 Spring开发者讨论,从0.9.0开始,我们将移去ContextHolder和SecureContext.有关详情请参考http://article.gmane.org/gmane.comp.java.springframework.devel/8290 和JIRA task SEC-77.这段历史之所以被提及是因为ContextHolder使用了很长一段时间,你所遇到某些有关Acegi安全系统的文档可能仍旧说到了 ContextHolder.通常你可以用SecurityContextHolder替代ContextHolder,用 SecurityContext替代SecureContext,这样你就能了解这些文档的主要意思.
1.4.2 SecurityContext
Acegi 安全系统用SecurityContextHolder来存储SecurityContext.SecurityContext包含有 Authentication的getter/setter方法.为了获得当前SecurityContext(或者参与者),所有Acegi安全类都要查询SecurityContextHolder.SecurityContextHolder是一个线程,这意味着它与当前线程的执行有关.
1.4.3 Context Storage
Acegi安全系统设计的重点就是在两个web请求之间存储SecurityContextHolder(它是SecurityContext的实现类)的内容.通过SecurityContextHolder获取存储在SecurityContext内Authentication,这样成功认证的参与者就能在随后的请求中鉴别到.HttpSessionContextIntegrationFilter一直存在到自动拷贝一个定义好 HttpSession属性的内容到SecurityContextHolder中,然后,在每一个请求的结尾,再将 SecurityContextHolder的内容拷贝回为下一个请求准备的HttpSession中.
将HttpSessionContextIntegrationFilter放在其它任何 Acegi安全过滤器之前(没有这样做是终端用户很容易犯的一个共同的错误)是必须的.当觉得合适时,Acegi安全过滤器期望能够修改 SecurityContextHolder的内容,并且如果有必要其它的类(也就是 HttpSessionContextIntegrationFilter)将在请求之间存储这些信息.这就是为什么 HttpSessionContextIntegrationFilter必须是第一个被用的过滤器.
通过设置HttpSessionContextIntegrationFilter bean中的context属性,你能定义一个特定的SecurityContext实现类用到你的应用中.
1.4.4 本地化
从1.0.0 开始,Acegi安全系统支持异常消息的本地化,这是终端用户很希望看到的特性.例如认证失败和存取被否定(授权失败)异常的本地化.那些面向开发者或系统配置员的异常和日志(包括不正确的属性、接口约定违背、使用不正确的构造函数、启动时间确认、调试级的日志)等没有本地化,而是用英语硬编码在 Acegi安全系统的代码内.
在org.acegisecurity包内装载的acegi-security-xx.jar是一个 messages.properties文件。当Acegi安全类实现Spring的MessageSourceAware接口,并且希望消息解析器在应用上下文启动时被依赖注入时,它就会用到。通常你所需要做的只是在你的应用上下文中注册一个bean来指向这些消息。下面是一个示例。
<bean id="messageSource"
class="org.springframework.context.support.ReloadableResourceBundleMessageSource">
<property name="basename"><value>org/acegisecurity/messages</value></property>
</bean>
messages.properties 文件的命名符合标准资源约束,用Acegi安全消息支持的默认的语言来表示并在上述的bean定义中注册它。在这个文件内没有许多的消息关键词,因此本地化不应认为是一个主要的问题。如果你打算对这个文件进行本地化,请考虑登记一个JIRA任务与社区共享你的工作,并为 messages.properties文件的本地化版本附上一个适当的名字。
有关本地化的讨论是一个叫 org.springframework.context.i18n.LocaleContextHolder的Spring 线程,Acegi通过使用 Locale来获得这个线程,从而对一个消息源中的消息实行本地化。Spring文档为你提供了有关使用LocaleContextHoldert和能自动设置它的帮助类(如AcceptHeaderLocaleResolver, CookieLocaleResolver, FixedLocaleResolver, SessionLocaleResolver 等)的更详细的情况.
1.5 安全性拦截
1.5.1所有的安全对象
如在系统设计部分描述的那样,每一个安全对象都有它自己的安全性拦截器来负责处理每一个请求
|
处理过程包括以下这些操作:
- 存储每一个安全请求的配置属性。
- 从相应的ObjectDefinitionSource提取 ConfigAttributeDefinition应用到请求中。
- 从SecurityContext中获取Authentication对象,这个对象保存在SecurityContextHolder中。
- 传递Authentication对象到AuthenticationManager中,通过响应更新SecurityContextHolder。
- 传递Authentication 对象、ConfigAttributeDefinition和安全对象到AccessDecisionManager中。
- 传递Authentication 对象、ConfigAttributeDefinition和安全对象到RunAsManager中。
- 如果RunAsManager对象返回一个新的Authentication 对象,就用它更新SecurityContextHolder对象。
- 继续执行安全对象的请求。
- 如果上一步中RunAsManager对象返回一个新的Authentication对象,就用它更新以前从AuthenticationManager中返回的SecurityContextHolder对象。
- 如果定义了AfterInvocationManager,把安全对象的执行结果传递给它。从而它可以抛出AccessDeniedException异常,接受可能需要的返回对象。
- 返回从AfterInvocationManager处收到的任何结果,或如没定义AfterInvocationManager,就返回一个安全对象执行后提供的简单结果。
|
虽然看起来有点麻烦,但别担心;开发者与安全处理的交互只是简单地使用几个基本的接口(如AccessDecisionManager)实现来完成的。下面我们将详细的讨论它们。
AbstractSecurityInterceptor处理了上述过程的大部分。如图一所示,每个安全对象都有它自已的安全性拦截器,它们是AbstractSecurityInterceptor的子类。这些安全对象--具体的安全性拦截器都将在下面详细讨论。
1.5.2 AOP Alliance(MethodInvocation)安全性拦截器
为了对MethodInvocations对象提供保护,开发者只需要简单地加上恰当配置的方法安全性拦截器(MethodSecurityInterceptor)到你的应用中,然后将需要查询的bean链接到拦截器链中去。这种链接方法由Spring的 ProxyFactoryBean或BeanNameAutoProxyCreator来完成,就如Spring的许多其它部分的用法(请参考应用实例)一样,另外,Acegi安全系统也提供一个MethodDefinitionSourceAdvisor,它将使用Spring的 DefaultAdvisorAutoProxyCreator自动链接MethodSecurityInterceptor之前的任何bean到这个安全性拦截器;MethodSecurityInterceptor本身定义如下:
<bean id="bankManagerSecurity"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="validateConfigAttributes"><value>true</value></property>
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
<property name="runAsManager"><ref bean="runAsManager"/></property>
<property name="afterInvocationManager"><ref bean="afterInvocationManager"/></property>
<property name="objectDefinitionSource">
<value>
org.acegisecurity.context.BankManager.delete*=ROLE_SUPERVISOR,RUN_AS_SERVER
org.acegisecurity.context.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR,BANKSECURITY_CUSTOMER,RUN_AS_SERVER
</value>
</property>
</bean>
如上所示MethodSecurityInterceptor配置了三个引用:AuthenticationManager, AccessDecisionManager和RunAsManager。下面我们将分节讨论它们。在这个例子中我们将定义 AfterInvocationManager,虽然它是可选的。MethodSecurityInterceptor也是通过"配置属性"来配置的,这些配置属性用于不同的方法签名。有关配置属性的全面的论述已在本文的系统设计部分提供。
MethodSecurityInterceptor 有三种通过配置属性来配置的方法,第一种是经由属性编辑器和应用上下文,如上所示。第二种使用Jakarta Commons Attributes或 Java 5 Annotation在你的源代码中通过定义配置属性来配置。第三种方法是你自己写一个ObjectDefinitionSource,虽然这已超出本文档的范围。
不管使用哪种方法,ObjectDefinitionSource都会相应地返回一个ConfigAttributeDefinition对象,ConfigAttributeDefinition对象含有一个单独的安全方法的所有配置属性。
应用注意:MethodSecurityInterceptor.setObjectDefinitionSource()方法实际需要一个 MethodDefinitionSource实例,这是一个标志性接口,是ObjectDefinitionSource的一个子类。它简单地指示 ObjectDefinitionSource认到MethodInvocations。为了简单起见,我们将继续把 MethodDefinitionSource当作ObjectDefinitionSource,因为对大多数使用 MethodSecurityInterceptor用户来说,区别很小。
如果使用应用上下文属性编辑器方法(如上所示),要使用逗号来区隔不同应用于给定方法模式的配置属性.每一个配置属性都将赋值到它们本身的SecurityConfig对象中去.SecurityConfig对象已在系统设计部分讨论.
如果你使用Jakarta Commons Attributes方式,你的bean上下文配置将会是不同的,如下所示:
<bean id="attributes" class="org.springframework.metadata.commons.CommonsAttributes"/>
<bean id="objectDefinitionSource"
class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
<property name="attributes"><ref local="attributes"/></property>
</bean>
<bean id="bankManagerSecurity"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="validateConfigAttributes"><value>false</value></property>
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
<property name="runAsManager"><ref bean="runAsManager"/></property>
<property name="objectDefinitionSource"><ref bean="objectDefinitionSource"/></property>
</bean>
另外,你的源代码将包含akarta Commons Attributes的tag标记.用它来指示ConfigAttribute的一个具体实现.
下面这个例子使用SecurityConfig实现来表示配置属性,其结果与上面用属性编缉器方式的安全配置一样.
public interface BankManager {
/**
* @@SecurityConfig("ROLE_SUPERVISOR")
* @@SecurityConfig("RUN_AS_SERVER")
*/
public void deleteSomething(int id);
/**
* @@SecurityConfig("ROLE_SUPERVISOR")
* @@SecurityConfig("RUN_AS_SERVER")
*/
public void deleteAnother(int id);
/**
* @@SecurityConfig("ROLE_TELLER")
* @@SecurityConfig("ROLE_SUPERVISOR")
* @@SecurityConfig("BANKSECURITY_CUSTOMER")
* @@SecurityConfig("RUN_AS_SERVER")
*/
public float getBalance(int id);
}
如果你使用Spring Security Java 5 Annotations方式,你的bean上下文配置如下所示:
<bean id="attributes" class="org.acegisecurity.annotation.SecurityAnnotationAttributes"/>
<bean id="objectDefinitionSource"
class="org.acegisecurity.intercept.method.MethodDefinitionAttributes">
<property name="attributes"><ref local="attributes"/></property>
</bean>
<bean id="bankManagerSecurity"
class="org.acegisecurity.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="validateConfigAttributes"><value>false</value></property>
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
<property name="runAsManager"><ref bean="runAsManager"/></property>
<property name="objectDefinitionSource"><ref bean="objectDefinitionSource"/></property>
</bean>
另外,你的源代码将包含Acegi Java 5 Security Annotations,用它来表示ConfigAttribute,下面的例子使用@Secured注释来表示配置属性.其结果与上面用属性编缉器方式的安全配置相同.
import org.acegisecurity.annotation.Secured;
public interface BankManager {
/**
* Delete something
*/
@Secured({"ROLE_SUPERVISOR","RUN_AS_SERVER" })
public void deleteSomething(int id);
/**
* Delete another
*/
@Secured({"ROLE_SUPERVISOR","RUN_AS_SERVER" })
public void deleteAnother(int id);
/**
* Get balance
*/
@Secured({"ROLE_TELLER","ROLE_SUPERVISOR","BANKSECURITY_CUSTOMER","RUN_AS_SERVER" })
public float getBalance(int id);
}
也许你已经注意到在上面MethodSecurityInterceptor的配置例子中的validateConfigAttributes属性。当设置为true时(缺省值),在系统启动时MethodSecurityInterceptor将会校验设置的配置属性是否有效。它检查每一个配置属性能否被 AccessDecisionManager或RunAsManager处理。如果两者都不能处理给定的配置属性,将会被抛出一个异常。如果你使用 Jakarta Commons Attributes方式进行属性配置,你应将validateConfigAttributes属性设置为 false。
注意:当使用BeanNameAutoProxyCreator来创建所需的安全代理时,配置中必须把 proxyTargetClass属性设为True.否则,通过MethodSecurityInterceptor.invoke的检查方法是代理的调用者,而不是代理的目标.请注意这里引入了CGLIB的需求.下面请看BeanNameAutoProxyCreator使用的例子.
<bean id="autoProxyCreator"
class="org.springframework.aop.framework.autoproxy.BeanNameAutoProxyCreator">
<property name="interceptorNames">
<list><value>methodSecurityInterceptor</value></list>
</property>
<property name="beanNames">
<list><value>targetObjectName</value></list>
</property>
<property name="proxyTargetClass" value="true"/>
</bean>
1.5.3 AspectJ(连接点)安全性拦截器
AspectJ安全性拦截器与上一节讨论的AOP Alliance安全性拦截器非常相似.在这节中我们将只讨论它们的不同部分.
AspectJ 拦截器命名为AspectJSecurityInterceptor,不象AOP Alliance安全性拦截器通过代理依靠Spring应用上下文来设定那样,AspectJSecurityInterceptor通过AspectJ 编译器来设定.在同样的应用中使用这两种拦截器将很常见, AspectJSecurityInterceptor用于域对象实例,而AOP Alliance安全性拦截器用于服务层的安全.
首先考虑AspectJSecurityInterceptor在Spring应用上下文中是怎么配置的:
<bean id="bankManagerSecurity"
class="org.acegisecurity.intercept.method.aspectj.AspectJSecurityInterceptor">
<property name="validateConfigAttributes"><value>true</value></property>
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
<property name="runAsManager"><ref bean="runAsManager"/></property>
<property name="afterInvocationManager"><ref bean="afterInvocationManager"/></property>
<property name="objectDefinitionSource">
<value>
org.acegisecurity.context.BankManager.delete*=ROLE_SUPERVISOR,RUN_AS_SERVER
org.acegisecurity.context.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR,BANKSECURITY_CUSTOMER,RUN_AS_SERVER
</value>
</property>
</bean>
如你所见,除了类名,AspectJSecurityInterceptor与AOP Alliance安全性拦截器完全相同.的确,两个拦截器共享同样的 objectDefinitionSource,因为objectDefinitionSource是通过 java.lang.reflect.Methods起作用,而不是AOP特定库是的类来起作用的.
当然,在做存取决定时,你可以通过相关的AOP特定库调用来存取(例如MethodInvocation 或 JoinPoint),也可以考虑通过一系列附加的证书来实施存取控制.
接下来,需要定义一个AspectJ的aspect,例如:
package org.acegisecurity.samples.aspectj;
import org.acegisecurity.intercept.method.aspectj.AspectJSecurityInterceptor;
import org.acegisecurity.intercept.method.aspectj.AspectJCallback;
import org.springframework.beans.factory.InitializingBean;
public aspect DomainObjectInstanceSecurityAspect implements InitializingBean {
private AspectJSecurityInterceptor securityInterceptor;
pointcut domainObjectInstanceExecution(): target(PersistableEntity)
&& execution(public * *(..)) && !within(DomainObjectInstanceSecurityAspect);
Object around(): domainObjectInstanceExecution() {
if (this.securityInterceptor != null) {
AspectJCallback callback = new AspectJCallback() {
public Object proceedWithObject() {
return proceed();
}
};
return this.securityInterceptor.invoke(thisJoinPoint, callback);
} else {
return proceed();
}
}
public AspectJSecurityInterceptor getSecurityInterceptor() {
return securityInterceptor;
}
public void setSecurityInterceptor(AspectJSecurityInterceptor securityInterceptor) {
this.securityInterceptor = securityInterceptor;
}
public void afterPropertiesSet() throws Exception {
if (this.securityInterceptor == null)
throw new IllegalArgumentException("securityInterceptor required");
}
}
在上面的例子中,安全性拦截器应用到PersistableEntity的每一个实例中,PersistableEntity是一个抽象类没有显示出来(你可以用任何其它类或pointcut类来表示,只要你喜欢).这看起来有点古怪,proceed()需要调用AspectJCallback,在 around()函数体内的语句有着特殊的意义.当AspectJSecurityInterceptor想要目标对象继续执行时,它将调用这个匿名的 AspectJCallback.
你需要配置Spring来加载aspect,并把它与AspectJSecurityInterceptor关联起来,完成此任务的bean声明如下所示:
<bean id="domainObjectInstanceSecurityAspect"
class="org.acegisecurity.samples.aspectj.DomainObjectInstanceSecurityAspect"
factory-method="aspectOf">
<property name="securityInterceptor"><ref bean="aspectJSecurityInterceptor"/></property>
</bean>
好了,现在你可以在你应用程序的任何地方用你想要适配的任何意思去创建你的beans,它们都会用到这个安全性拦截器.
1.5.4 FilterInvocation(过滤器调用)安全性拦截器
为了对FilterInvocation对象提供保护,开发者需要在web.xml中添加一个拦截器,来代理SecurityEnforcementFilter。一个典型的配置例子如下所示:
<filter>
<filter-name>Acegi HTTP Request Security Filter</filter-name>
<filter-class>org.acegisecurity.util.FilterToBeanProxy</filter-class>
<init-param>
<param-name>targetClass</param-name>
<param-value>org.acegisecurity.intercept.web.SecurityEnforcementFilter</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>Acegi HTTP Request Security Filter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
注意,这里这个filter实际上是一个FilterToBeanProxy??从filter到bean的代理。Acegi安全系统中绝大多数的filter都使用这个类。将会在filter一节中讲述它的更多内容。
在应用上下文中,你需要配置三个bean:
<bean id="securityEnforcementFilter"
class="org.acegisecurity.intercept.web.SecurityEnforcementFilter">
<property name="filterSecurityInterceptor"><ref bean="filterInvocationInterceptor"/></property>
<property name="authenticationEntryPoint"><ref bean="authenticationEntryPoint"/></property>
</bean>
<bean id="authenticationEntryPoint"
class="org.acegisecurity.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl"><value>/acegilogin.jsp</value></property>
<property name="forceHttps"><value>false</value></property>
</bean>
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
<property name="runAsManager"><ref bean="runAsManager"/></property>
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
\A/secure/super/.*\Z=ROLE_WE_DONT_HAVE
\A/secure/.*\Z=ROLE_SUPERVISOR,ROLE_TELLER
</value>
</property>
</bean>
当用户请求一个受保护的HTTP资源而没有通过认证时,authenticationEntryPoint就会被调用。这个类负责将适当的响应呈现给用户,因此,开始认证,Acegi安全系统为认证提供了三种具体的认证实现: AuthenticationProcessingFilterEntryPoint进行表单认证; BasicProcessingFilterEntryPoint进行HTTP基本认证处理; CasProcessingFilterEntryPoint进行耶鲁集中认证服务(CAS)登录. AuthenticationProcessingFilterEntryPoint和CasProcessingFilterEntryPoint有与使用HTTPS相关的可选属性.如有需要,请参考JavaDoc文档.
PortMapper提供了有关哪个HTTPS端口对应哪个 HTTP端口的信息.AuthenticationProcessingFilterEntryPoint和其它几个bean将使用这些信息. PortMapperImpl是其缺省的实现,它将常用的HTTP 80和8080端口映射对HTTPS相应的443和8443端口;如有需要,你可以定制这个映射.
SecurityEnforcementFilter主要在需要是提供会话管理支持和认证初始化.它把真正的FilterInvocation安全判别到配置到FilterSecurityInterceptor中.
象任何其它安全性拦截器一样,FilterSecurityInterceptor需要有一份AuthenticationManager, AccessDecisionManager 和 RunAsManager的引用,这些对象将在下文中分章节加以讨论. FilterSecurityInterceptor也要配置应用于不同HTTP URL请求的配置属性.有关这些配置属性的详细讨论已在本文的系统设计章节是提供.
FilterSecurityInterceptor的配置属性可以通过两种方式来配置.第一种是使用属性编辑和应用上下文的方式,就如上面显示的那样。第二种方式是书写你的ObjectDefinitionSource,这超过了本文的范围。不管使用那种方式, ObjectDefinitionSource负责返回一个ConfigAttributeDefinition对象,这个对象包括了一个单个 http url请求所关联的所有配置属性信息。
应该注意到的是FilterSecurityInterceptor.setObjectDefinitionSource()实际盼望得到的是一个FilterInvocationDefinitionSource实例。这是一个继承了ObjectDefinitionSource的标记性接口。它简单的表示ObjectDefinitionSource理解FilterInvocations。简洁的讲,我们只需要继续使用 FilterInvocationDefinitionSource,就像使用ObjectDefinitionSource一样,因为他们之间的区别就 FilterSecurityInterceptor的角度来看是非常小的。
如果使用应用上下文属性编辑的方式(如上所示),应用到不同的http rul上的不同配置属性需要被逗号分割开。每一个配置属性都会分配到它自己的SecurityConfig对象中。 SecurityConfig对象在系统设计部分已经被讨论过。属性编缉器创建的ObjectDefinitionSource以及 FilterInvocationDefinitionSource要与依赖于FilterInvocations的配置属性相匹配. FilterInvocations用于验证所请求的URL表达是否正确.
在使用配置属性对请求的进行匹配和处理时,有两种标准表达式语法可以使用。缺省的是使用正则表达式规则处理所有的表达式,另外,显示的 PATTERN_TYPE_APACHE_ANT标明将导致所有的表达式都使用 Apache Ant paths方式进行处理。同一定义内同时使用两种方式是不允许的。例如上面配置可以被Apache Ant paths 方式书写成以下形式:
<bean id="filterInvocationInterceptor"
class="org.acegisecurity.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager"><ref bean="authenticationManager"/></property>
<property name="accessDecisionManager"><ref bean="accessDecisionManager"/></property>
<property name="runAsManager"><ref bean="runAsManager"/></property>
<property name="objectDefinitionSource">
<value>
CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON
PATTERN_TYPE_APACHE_ANT
/secure/super/**=ROLE_WE_DONT_HAVE
/secure/**=ROLE_SUPERVISOR,ROLE_TELLER
</value>
</property>
</bean>
不管使用哪种表达式语法,表达式都总是按照它们定义的顺序进行处理。所以越详细的定义越要放在定义的前面,这十分重要。上面的例子中对这一点体现的很清楚。更具体的/secure/super/模式出现在没那么具体的/secure之前,如果反过来,将总是与/secure模式匹配,而 /secure/supper将永远不会生效.
特殊的关键字CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON将会导致 FilterInvocationDefinitionSource在于表达式进行匹配之前会自动转换请求url为小写。缺省情况下请求url是不会被进行转换的,通常情况下建议使用CONVERT_URL_TO_LOWERCASE_BEFORE_COMPARISON并且书写表达式时都使用小写。
就像其他的安全拦截器一样,validateConfigAttributes属性 是可以进行设置的。当设置为true时(缺省值),在系统启动时 MethodSecurityInterceptor将会校验设置的配置属性是否有效。它检查每一个配置属性能否被 AccessDecisionManager或RunAsManager处理。如果两者都不能处理给定的配置属性,将会被抛出一个异常.
1.6认证
1.6.1 认证请求
认证需要用一种客户端代码的方式来把安全性鉴别信息送到Acegi安全系统中.这就是Authentication接口扮演的角色。 Authentication接口包括三个重要的对象:principal(调用者的身份),credentials(证明调用者身份的证据,例如密码),授权给principal的权限(列表)。principal 和它的credentials有客户端代码提供,授权列表由认证管理器 (AuthenticationManager)提供。
500)this.width=500" align=absMiddle border=0>
如图3所示,Acegi安全性系统包括几个具体的认证(Authentication)实现类:
UsernamePasswordAuthenticationToken |
允许使用用户名(username)和密码(password)来表示调用者(principal)和他的证书(credentials)。它们也是由 HTTP Session Authentication负责创建的。 |
TestingAuthenticationToken |
主要用作单元测试,它会为相应的AuthenticationProvider自动考虑一个认证对象。 |
RunAsUserToken |
被作为默认的run-as认证置换实现。这个将在Run-As Authentication Replacement那节中进一步讨论。 |
CasAuthenticationToken |
用于描述一次成功的耶鲁集中认证服务 (CAS)。这将在CAS章节中进一步讨论。 |
PrincipalAcegiUserToken 和JettyAcegiUserToken |
实现了AuthByAdapter (Authentication的子类)并且它们是在认证过程已经被 Acegi系统的容器适配器完成后被使用的。这部分也将在容器适配器章节中作进一步讨论。 |
对于主体(principal)的授权工作是通过GrantedAuthority进行的。GrantedAuthority接口将在下面关于授权的章节中被讨论。
1.6.2 认证管理器
在安全拦截器一节中我们提到AbstractSecurityInterceptor从SecurityContextHolder中的 SecurityContext里面提取Authentication对象。紧接着就将它传递给AuthenticationManager。 AuthenticationManager接口十分的简单:
public Authentication authenticate(Authentication authentication) throws AuthenticationException;
AuthenticationManager 的实现在认证失败的时候会抛出一个异常(AuthenticationException),或者成功时返回一个组装成功的Authentication 对象。特别要注意,返回的Authentication对象应该包括一组GrantedAuthority对象。安全拦截器将返回的、组装后的 Authentication对象放回到SecurityContextHolder中的SecurityContext里,用以替换原来的那个 Authentication对象。
AuthenticationException有几个子类,其中最重要的是 BadCredentialsException (主体(principal)或者(证据)credentials错误), DisabledException和LockedException.后面两个异常表示主体已经发现(principal)但是证据没有通过检验还有认证被否决、拒绝。AuthenticationServiceException也会被使用,它表明认证系统不能处理请求(例如数据库不可用引起)。 AuthenticationException 还有CredentialsExpiredException和 AccoungtExpiredException两个子类,虽然它们不是很常用。
1.6.3 基于供方的认证
尽管最基本的Authentication和AuthenticationManager接口使用户可以开发出自己的认证系统,但是用户完全可以考虑使用 Acegi安全系统提供的基于供方的认证包。其关键类??ProviderManager通过一组认证提供者 (AuthenticationProviders)的形式在bean定义中被配置:
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
<list>
<ref bean="daoAuthenticationProvider"/>
<ref bean="someOtherAuthenticationProvider"/>
</list>
</property>
</bean
ProviderManager 将调用一系列已注册的AuthenticationProvider实现,直到找到一个表明它能够认证给定的Authentication类为止。当找到第一个符合条件的AuthenticationProvider时,它就把它传递给认证请求。AuthenticationProvider要么抛出一个 AuthenticationException异常,要么返回一个组装成功的Authentication对象.
ProviderManager 还有几个其它重要的函数,它集成了并发会话处理支持,它也能将任何由AuthenticationProvider抛出的异常转化和发布一个恰当的事件。这些事件可在org.acegisecurity.event.authentication包中找到。高级用户能够配置 ProviderManager.exceptionMappings属性从而将不同的异常映射为不同的事件(通常不需要这么做,缺省的事件传播是恰当的??特别是当你没在ApplicationContext中配置ApplicationListener时,事件就会简单地忽略掉)
Acegi安全系统所提供的几个AuthenticationProvider实现类:
TestingAuthenticationProvider 能够对TestingAuthenticationToken进行验证。这种认证的限制是它不管TestingAuthenticationToken中包含什么,它都认为是有效的。这就使得它在进行单元测试时变得非常有用,因为你可以创建一个Authentication对象---它正好对你需要进行测试的方法进行了授权。这样你就可以不需要在产品系统中注册AuthenticationProvider而能完成需要的测试环境搭建了。
DaoAuthenticationProvider |
可以对通过对数据库数据的存取,验证UsernamePasswordAuthenticationToken。这个方面将在下面的章节进行讨论,因为它是进行验证处理的主要工作方式。 |
RunAsImplAuthenticationProvider |
用以验证RunAsUserToken。这个将在下面Run-As Authentication Replacement章节进行讨论。如果你不使用 run-as处理机制,你不需要注册此AuthenticationProvider。 |
AuthByAdapterProvider |
能够验证任何的AuthByAdapter (a subclass of Authentication used with container adapters)。这部分内容将在下面的章节进行讨论。如果你不使用容器适配器,你不需要注册此AuthenticationProvider。 |
CasAuthenticationProvider |
用来验证CAS,将在下面章节进行讨论。 |
JaasAuthenticationProvider |
将会将认证请求指派到JAAS登录模块,这将在下文进行讨论。 |
1.6.4 并发会话支持
Acegi安全系统具有阻止同一主体并发访问同一web应用资源的能力。例如:你可以阻止用户"batman"同一时间内两次进行系统登录。
为了使用并发会话支持,你需要在web.xml是加入下列语句:
<listener>
<listener-class>org.acegisecurity.ui.session.HttpSessionEventPublisher</listener-class>
</listener>
另外,你需要将org.acegisecurity.concurrent.ConcurrentSessionFilter加入到你的 FilterChainProxy中。并发会话过滤器需要配置唯一一个sessionRegistry的属性,它通常指向 SessionRegistryImpl实例。
每次HttpSession开始或终止时,web.xml中的 HttpSessionEventPublish都会产生一个ApplicationEvent事件并通知Spring的 ApplicationContext.这非常关键,因为它允许SessionRegistryImpl注意到会话会在何时结束。
你也需要建立ConcurrentSessionControllerImpl,并在ProviderManager中引用它。
<bean id="authenticationManager" class="org.acegisecurity.providers.ProviderManager">
<property name="providers">
</property>
<property name="sessionController"><ref bean="concurrentSessionController"/></property>
</bean>
<bean id="concurrentSessionController" class="org.acegisecurity.concurrent.ConcurrentSessionControllerImpl">
<property name="maximumSessions"><value>1</value></property>
<property name="sessionRegistry"><ref local="sessionRegistry"/></property>
</bean>
<bean id="sessionRegistry" class="org.acegisecurity.concurrent.SessionRegistryImpl"/>
1.6.5 数据存取对象认证提供者
Acegi 安全系统包含一个产品级的认证提供者(AuthenticationProvider)实现??DaoAuthenticationProvider,这个认证提供者通过获得在创建bean时配置在数据存取对象中的认证详细信息,从而能够认证 UsernamePasswordAuthenticationToken
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService"><ref bean="inMemoryDaoImpl"/></property>
<property name="saltSource"><ref bean="saltSource"/></property>
<property name="passwordEncoder"><ref bean="passwordEncoder"/></property>
</bean>
其中PasswordEncoder和SaltSource是可选的,PasswordEncoder负责对认证库中的密码进行加解密.而 SaltSource则是在产生密码时给它加点"盐",以增强密码在认证库中的安全性.Acegi安全系统提供的PasswordEncoder实现中包括MD5、SHA和明文编码。Acegi安全系统也提供两个SaltSource的实现:SystemWideSaltSource,它用同样的"盐"对系统中所有的密码进行编码,而ReflectionSaltSource只对从UserDetails对象返回的获得这种"盐"的指定属性进行检查。请参考JavaDoc以获取对这些可选特性的更详细信息。
除上属性外,DaoAuthenticationProvider也支持对 UserDetails对象的缓存。UserCache接口使DaoAuthenticationProvider能把UserDetails对象放进缓存并可在接下来对相同用户进行认证时在缓存中找回它。在缺省时DaoAuthenticationProvider使用NullUserCache,表示没有使用Cache.Acegi也提供了一个可用的缓存实现??EhCacheBasedUserCache,其配置如下:
<bean id="daoAuthenticationProvider" class="org.acegisecurity.providers.dao.DaoAuthenticationProvider">
<property name="userDetailsService"><ref bean="userDetailsService"/></property>
<property name="userCache"><ref bean="userCache"/></property>
</bean>
<bean id="cacheManager" class="org.springframework.cache.ehcache.EhCacheManagerFactoryBean">
<property name="configLocation">
<value>classpath:/ehcache-failsafe.xml</value>
</property>
</bean>
<bean id="userCacheBackend" class="org.springframework.cache.ehcache.EhCacheFactoryBean">
<property name="cacheManager">
<ref local="cacheManager"/>
</property>
<property name="cacheName">
<value>userCache</value>
</property>
</bean>
<bean id="userCache" class="org.acegisecurity.providers.dao.cache.EhCacheBasedUserCache">
<property name="cache"><ref local="userCacheBackend"/></property>
</bean>
所有Acegi安全系统的EH-CACHE实现中(包括EhCacheBasedUserCache),都需要一个EH-CACHE cache对象,这个 cache对象可在你喜欢的任何地方获得,虽然我们推荐你使用Spring的工厂类如上配置中所示。如使用Spring的工厂类,请参考Spring文档以获取如何优化的更详细的细节,如缓存存储本地化、内存使用、退出策略、超时设定等。
一个能为DaoAuthenticationProvider提供存取认证库的的类,它必须要实现UserDetailsService接口:
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException, DataAccessException;
UserDetails 是一个接口,它能提供一系列get函数以获得认证时需要的基本认证信息如用户名、密码、所获得的授权、是否禁用等;一个具体的实现就是User类. Acegi用户需要决定什么时候写他们的UserDetailsService接口以及返回的UserDetails是什么类型。在大多数情况下将使用 User及其子类,虽然在特定的环境中(如对象关系映射器)也许需要用户写他们自己的UserDetails实现。UserDetails也经常用于存储一些与调用者相关的附加信息(如电话号码、email地址等)因此它们经常在web视图中使用。
如果UserDetailsService实现起来很简单,用户应当很容易从他们选择的持久化策略中找回认证信息。
DaoAuthenticationProvider设计没有考虑支持帐户锁定的功能,因为那将增加UserDetailsService接口的复杂性。例如需要增加不成功认证的计数,这些功能可能通过补充应用程序的公布特性而很容易提供,这些特性将在下面讨论。
DaoAuthenticationProvider 返回一个Authentication对象,它含有principal的属性集。principal要么是一个字串(是必需的用户名),要么是一个 UserDetails对象(从UserDetailsService中查找),缺省情况下返回UserDetails。因为这将使应用能加上额外的属性以便在应用中使用,如用户的全名,Email地址等。如使用容器适配器,或在应用中使用Strings(在Acegi 0.6之前的版本就是这样)你就应在你的应用上下文中把 DaoAuthenticationProvider.forcePrincipalAsString属性设为true。
1.6.6 In-Memory 认证
虽然使用DaoAuthenticationProvider,从持久化引擎中获取信息来创建定制的UserDetailsService实现很容易,但许多应用不需要这么复杂,一种替代方式是使用InMemoryDaoImpl在应用上下文中配置认证库:
<bean id="inMemoryDaoImpl" class="org.acegisecurity.userdetails.memory.InMemoryDaoImpl">
<property name="userMap">
<value>
marissa=koala,ROLE_TELLER,ROLE_SUPERVISOR
dianne=emu,ROLE_TELLER
scott=wombat,ROLE_TELLER
peter=opal,disabled,ROLE_TELLER
</value>
</property>
</bean>
1.6.7 JDBC认证
1.6.8 JAAS认证
1.6.9 Siteminder认证
1.6.10 使用认证的几点建议
---------------------------------------------------------------------------------------------------------------------------------
说人之短,乃护己之短。夸己之长,乃忌人之长。皆由存心不厚,识量太狭耳。能去此弊,可以进德,可以远怨。
http://www.blogjava.net/szhswl
------------------------------------------------------------------------------------------------------ ----------------- ---------
posted on 2007-12-21 09:20
宋针还 阅读(451)
评论(0) 编辑 收藏 所属分类:
ACEGI