本文档是"Spring框架下Acegi安全系统"(Acegi
Security System for Spring)的一份参考指南,Acegi安全系统是由一序列类构成,这些类为Spring框架提供认证和授权服务。
第一章 安全
1 : 准备
Acegi通过对流行的WEB容器的可选集成而为使用Spring编写的项目提供认证与授权的能力.这种安全架构是全部用"Spring方式"开发,包括使用bean
contexts、拦截器和接口驱动的编程方式。结果,对那些为基于Spring应用的寻求安全保证应用来说,Acegi是一个非常有用的外置式的安全架构,它很容易适用于各种复杂的用户化需求。
安全系统包括两个显而易见的方面:认证和授权。
1
认证 :解决调用者是否是他们所声称的人的问题
2
授权 :是关于已经经过认证的调用者是否允许进行给定操作的问题。
在Acegi中,需要认证的
都作为一个"参与者"来题及。这个安全架构没有你在其它安全系统是所熟悉的
角色
组
的概念,即使Acegi完全提供相应的功能。
2 : 系统设计
大多数企业级的应用有四个基本安全需求。
首先,他们要能够认证一个访问者;
其次,他们需要有能力对web请求提供安全保证;
第三,企业级应用需要有能力对服务层方法提供安全保证;
最后,企业级应用经常要有能力对域对象实例提供安全保护。
Acegi提供了一个通用的框架,来满足所有这四个通用的企业级应用安全需求。
Acegi基本上由八个关键功能部分组成 :
1
:Authentication对象 它包括参与者、认证证书和赋予参与者的权限。这个对象也能存储一些与认证请求有关的附加信息。例如源TCP
/
IP地址.
2
:SecurityContextHolder对象 它包含一个在线程级对象内的Authentication 认证对象
3
: AuthenticationManager对象 它鉴别由ContextHolder引进的Authentication对象.
4
: AccessDecisionManager对象 授权一个给定的操作。(投票通过策略管理器)
5
: RunAsManager对象 当一个给定的操作被执行时,任意替代Authentication 对象
6
:
"
安全对象
"
拦截器AbstractSecurityInterceptor :
配合认证、授权、run -
as替换,从而调用处理执行给定的操作。
7
: AfterInvocationManager对象 能够修改一个从
"
安全对象
"
调用中返的对象。例如删去一些参与者没有授权存取的集合元素。
8
: 存取控制列表(ACL)管理包 用于获取应用于域对象实例ACLs。
"安全对象"拦截器AbstractSecurityInterceptor执行Acegi安全系统中的大多数关键类。这样做是为了利用框架的主要特性。由于它的重要性,图1表示了AbstractSecurityInterceptor
拦截器类的主要关系和具体实现。
每个"安全对象"拦截器(下文中称为"保护性拦截器")作用于一个特殊类型的"安全对象"。那么,什么是安全对象?安全对象是指对它采取了安全保护措施的任何类型的对象。一个安全对象必须提供某种形式回调,这样当需要的时候保护性拦截器就能明显地起作用。保护性拦截器回调对象同时继续所请求的操作。如果安全对象不能提供一个本地的回调方法,就要写一个包装器来实现它。
每个安全对象都有一个实现它的包放在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.
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 bean中的context属性,你能定义一个特定的SecurityContext实现类用到你的应用中.
1.4.4 本地化
从1.0.0 开始,Acegi安全系统支持异常消息的本地化,这是终端用户很希望看到的特性.例如认证失败和存取被否定(授权失败)异常的本地化.
那些面向开发者或系统配置员的异常和日志(包括不正确的属性、接口约定违背、使用不正确的构造函数、启动时间确认、调试级的日志)等没有本地化,而是用英语硬编码在
Acegi安全系统的代码内.
在org.acegisecurity包内装载的acegi-security-xx.jar是一个 messages.properties文件,内容如下(节选)
:
DaoX509AuthoritiesPopulator.noMatching
=
No matching pattern was found in subjectDN:
{ 0
}
RememberMeAuthenticationProvider.incorrectKey
=
The presented RememberMeAuthenticationToken does not contain the expected key
RunAsImplAuthenticationProvider.incorrectKey
=
The presented RunAsUserToken does not contain the expected key
DigestProcessingFilter.missingMandatory
=
Missing mandatory digest value; received header
{ 0
}
DigestProcessingFilter.missingAuth
=
Missing mandatory digest value
for
'
auth
'
QOP; received header
{ 0
}
DigestProcessingFilter.incorrectRealm
=
Response realm name
{ 0
}
does not match system realm name of
{ 1
}
DigestProcessingFilter.nonceExpired
=
Nonce has expired
/
timed out
.
当Acegi安全类实现Spring的MessageSource接口,并且希望消息解析器在应用上下文启动时被依赖注入时,它就会用到。
通常你所需要做的只是在你的应用上下文中注册一个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所有的安全对象
如在系统设计部分描述的那样,每一个安全对象都有它自己的安全性拦截器来负责处理每一个请求 处理过程包括以下这些操作:
虽然看起来有点麻烦,但别担心;开发者与安全处理的交互只是简单地使用几个基本的接口(如AccessDecisionManager)实现来完成的。下面我们将详细的讨论它们。
安全拦截器 AbstractSecurityInterceptor处理了上述过程的大部分。如图一所示,每个安全对象都有它自已的安全性拦截器,它们是AbstractSecurityInterceptor的子类。这些安全对象--具体的安全性拦截器都将在下面详细讨论。
1.5.2 AOP Alliance(MethodInvocation)安全性拦截器
为了对MethodInvocations对象提供保护,开发者只需要简单地加上恰当配置的方法安全性拦截器(MethodSecurityInterceptor)到你的应用中,
然后将需要查询的bean链接到拦截器链中去。这种链接方法由Spring的 ProxyFactoryBean或BeanNameAutoProxyCreator来完成,就如Spring的许多其它部分的用法(请参考应用实例)一样,
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
>
另外,Acegi安全系统也提供一个MethodDefinitionSourceAdvisor,它将使用Spring的 DefaultAdvisorAutoProxyCreator自动链接MethodSecurityInterceptor之前的任何bean到这个安全性拦截器;
如上所示MethodSecurityInterceptor配置了三个引用:
在这个例子中我们将定义 :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
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一节中讲述它的更多内容。
补充:appfuse这部分的配置是 :
<
filter
>
<
filter
-
name
>
securityFilter
</
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
>
securityFilter
</
filter
-
name
>
<
url
-
pattern
>/
j_security_check
</
url
-
pattern
>
</
filter
-
mapping
>
<
filter
-
mapping
>
<
filter
-
name
>
securityFilter
</
filter
-
name
>
<
url
-
pattern
>/
dwr
/**/
/* </url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>securityFilter</filter-name>
<url-pattern>*.html</url-pattern>
</filter-mapping>
<filter-mapping>
<filter-name>securityFilter</filter-name>
<url-pattern>*.jsp</url-pattern>
</filter-mapping>
在应用上下文中,你需要配置三个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.5.4
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.6认证
1.6.1 认证请求
认证需要用一种客户端代码的方式来把安全性鉴别信息送到Acegi安全系统中.这就是Authentication接口扮演的角色。
Authentication接口包括三个重要的对象:
principal(调用者的身份)
credentials(证明调用者身份的证据,例如密码)
授权给principal的权限(列表)。
principal 和它的credentials有客户端代码提供,授权列表由认证管理器
(AuthenticationManager)提供。
|