安全对象的实现
23.1. AOP Alliance安全拦截器(MethodInvocation)
在Spring Security 2.0之前,对MethodInvocation
进行安全管理需要很多恼人的配置。现在推荐的
method security
的方法是使用命名空间配置。通过这种方式,
method security
需要的基础设施对象会自动为你配置,你不需要真正知道实现的类。我们在此只提供这些类的一个快速概览。
method security要求使用一个MethodSecurityInterceptor
,用来提供
MethodInvocation
的安全控制。根据配置方式的不同,一个拦截器可以指定给一个对象,或在多个对象间共享。拦截器使用一个
MethodDefinitionSource
实例来获取应用在特别方法调用上的配置属性。
MapBasedMethodDefinitionSource
用来保存配置属性,以方法名称为
key
(可用通配符),当属性是用
<intercept-methods>
或<protect-point>
元素定义的时候在内部使用。其他实现会在处理
annotation-based配置中使用。
23.1.1. 显式MethodSecurityIterceptor配置
你当然也可以直接配置MethodSecurityIterceptor
,和
Spring AOP
的代理机制一起使用:
<bean id="bankManagerSecurity"
class="org.springframework.security.intercept.method.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="objectDefinitionSource">
<value>
org.springframework.security.context.BankManager.delete*=ROLE_SUPERVISOR
org.springframework.security.context.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR
</value>
</property>
</bean>
23.2. AspectJ安全拦截器 (JoinPoint)
AspectJ安全拦截器跟前面一节中讨论的AOP Alliance安全拦截器很相似。这里我们只讨论不同之处。
AspectJ拦截器叫AspectJSecurityInterceptor
。跟
AOP Alliance安全拦截器依赖Spring Application Context的配置通过代理来进行织入不同,AspectJSecurityInterceptor
是通过
AspectJ编译器来进行织入的。在同一个应用中,这两种安全拦截器的用法没有什么大的不同。
让我们先看看AspectJSecurityInterceptor
是如何配置到
Spring
中的:
<bean id="bankManagerSecurity"
class="org.springframework.security.intercept.method.aspectj.AspectJSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="objectDefinitionSource">
<value>
org.springframework.security.context.BankManager.delete*=ROLE_SUPERVISOR
org.springframework.security.context.BankManager.getBalance=ROLE_TELLER,ROLE_SUPERVISOR
</value>
</property>
</bean>
正如你所见到的,除了类名,AspectJSecurityInterceptor
的配置跟
AOP Alliance安全拦截器配置完全一样。实际上这两种拦截器能共用同一个objectDefinitionSource
,因为
ObjectDefinitionSource
是调用
java
标准的
java.lang.reflect.Method
而非AOP库特有的类。当然,你的访问决议可以访问AOP库的特定类(例如MethodInvocation
或 JoinPoint
),这样当进行访问决议的时候可以考虑添加一系列的标准(例如方法的参数)。
接着你需要定义一个AspectJ方面。例如:
package org.springframework.security.samples.aspectj;
import org.springframework.security.intercept.method.aspectj.AspectJSecurityInterceptor;
import org.springframework.security.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
实例上,这是一个没提到过的抽象类(你可以使用任何你喜欢的其他类或
pointcut
表达式)。因为
proceed();
语句仅在
around()
体内才有特别意义,那种情况需要
AspectJCallback
。当它想目标对象继续的时候,
AspectJSecurityInterceptor
调用这个匿名
AspectJCallback
类。
你需要配置Spring装入AOP Aspect并注入AspectJSecurityInterceptor
。下面的
bean
声明可以做到这个:
<bean id="domainObjectInstanceSecurityAspect"
class="org.springframework.security.samples.aspectj.DomainObjectInstanceSecurityAspect"
factory-method="aspectOf">
<property name="securityInterceptor"><ref bean="aspectJSecurityInterceptor"/></property>
</bean>
这样就大功告成了!现在你可以在应用的任何地方创建你的bean了,使用任何你认为合适的方法(如new Person();
),他们会应用安全拦截器。
23.3. FilterInvocation安全拦截器
要对FilterInvocation
进行安全控制,开发人员要添加一个FilterSecurityInterceptor
到
filter chain
中。下面是一个典型的配置:
在application context中,你需要配置如下对象:
<bean id="exceptionTranslationFilter"
class="org.springframework.security.ui.ExceptionTranslationFilter">
<property name="authenticationEntryPoint" ref="authenticationEntryPoint"/>
</bean>
<bean id="authenticationEntryPoint"
class="org.springframework.security.ui.webapp.AuthenticationProcessingFilterEntryPoint">
<property name="loginFormUrl" value="/acegilogin.jsp"/>
<property name="forceHttps" value="false"/>
</bean>
<bean id="filterSecurityInterceptor"
class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="objectDefinitionSource">
<security:filter-invocation-definition-source>
<security:intercept-url pattern="/secure/super/**" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern="/secure/**" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-invocation-definition-source>
</property>
</bean>
ExceptionTranslationFilter
在
java exception
和
http
响应之间提供了一个桥梁。它纯粹关注维护用户界面。该过滤器并不做任何实际的安全操作。如果一个
AuthenticationException
被检测到,它会调用
AuthenticationEntryPoint来开始认证过程(例如,用户登录)。
如果用户请求一项安全控制下的
http
资源但又还没通过认证,就会调用
AuthenticationEntryPoint
。这个类处理给用户展示一个适当的界面以便用户可以开始认证过程。
Spring Security提供了3个具体的实现:AuthenticationProcessingFilterEntryPoint
,用于开始一个
form-based
认证;
BasicProcessingFilterEntryPoint
,用于开始一个基于
HTTP
的认证过程;
CasProcessingFilterEntryPoint
,用于开始一个
JA-SIG Central Authentication
Service (CAS)登录。AuthenticationProcessingFilterEntryPoint
和
CasProcessingFilterEntryPoint
都有可选的属性,可强制使用
HTTPS
,如果你需要了解这些,请参考
JavaDocs。
FilterSecurityInterceptor
负责处理
HTTP
资源的安全。与其他安全拦截器一样,它也需要设置
AuthenticationManager
和
AccessDecisionManager
,这两个都会在后面的部分进行讨论。
FilterSecurityInterceptor
也配置了应用到不同的
HTTP URL
请求的属性。关于配置属性的一个完整讨论在本文档的“概要设计”部分提供。
FilterSecurityInterceptor
可以通过两种方式进行配置。第一种方式是上面已经用到的,是使用
<filter-invocation-definition-source>
命名空间元素。这跟用
<filter-chain-map>
来配置
FilterChainProxy
相似,但是
<intercept-url>
子元素只使用
pattern
和
access
属性。第二种方式是通过编写你自己的
ObjectDefinitionSource
,不过这已经不是本文档讨论的范畴。不论使用何种方式,
ObjectDefinitionSource
都是负责返回一个
ConfigAttributeDefinition
对象,其中包含了所有跟该安全
HTTP URL
相关的配置属性。
应该要注意到FilterSecurityInterceptor.setObjectDefinitionSource()
方法实际上需要一个
FilterInvocationDefinitionSource
实例。这是一个标记接口,他是继承于
ObjectDefinitionSource
。他只是简单表明这个
ObjectDefinitionSource
能够理解
FilterInvocation
。为了保持简单,我们这里继续使用
FilterInvocationDefinitionSource
作为
ObjectDefinitionSource
,对大部分
FilterSecurityInterceptor
的用户来说,他们之间的差异很小。
当使用命名空间方式来配置拦截器的时候,用逗号来分隔每个应用在HTTP URL上的配置属性。每个配置属性都被指定给它自己的SecurityConfig
对象。
SecurityConfig
对象在概要设计部分有进行讨论。
ObjectDefinitionSource
是由
property editor
创建的,
FilterInvocationDefinitionSource
的配置属性与基于对请求
URL
的表达式评估所得的
FilterInvocations
的配置属性对应。支持两种标准的表达式语法,缺省是把表达式当作
Apache Ant path表达式,为了应付复杂的情况,正则表达式也被支持。path-type
属性用来指明使用的pattern表达式类型。不能在同一个定义里混用两种表达式。下面是一个例子,前面的配置中我们使用了Ant path表达式,下面我们用正则表达式来实现:
<bean id="filterInvocationInterceptor"
class="org.springframework.security.intercept.web.FilterSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="runAsManager" ref="runAsManager"/>
<property name="objectDefinitionSource">
<security:filter-invocation-definition-source path-type="regex">
<security:intercept-url pattern=""A/secure/super/.*"Z" access="ROLE_WE_DONT_HAVE"/>
<security:intercept-url pattern=""A/secure/.*"" access="ROLE_SUPERVISOR,ROLE_TELLER"/>
</security:filter-invocation-definition-source>
</property>
</bean>
不管采用何种表达式,都是根据它们定义的顺序来进行解析的。因此把其中较为具体的表达式放在前面就比较重要了。这在我们上面的例子中也有反映出来。更加精确的/secure/super/
放在了
/secure/
前面。如果这两个调转过来,
/secure/
会总是在
/secure/super/
前面满足了条件,因此永远跑不到
/secure/super/
。
跟其它安全拦截器一起,validateConfigAttributes
属性会被读取并检查。如果设为
true(
缺省
)
,启动应用的时候
FilterSecurityInterceptor
就会检查提供的配置属性是否有效。它是通过检查每一项配置是否都能被
AccessDecisionManager
或
RunAsManager
处理来确定的。如果两个都处理不了,就抛出异常。