安全对象(Secure Object)的实现

本站(springdoc.cn)中的内容来源于 spring.io ,原始版权归属于 spring.io。由 springboot.io - Spring Boot中文社区 进行翻译,整理。可供个人学习、研究,未经许可,不得进行任何转载、商用或与之相关的行为。 商标声明:Spring 是 Pivotal Software, Inc. 在美国以及其他国家的商标。

本节介绍Spring Security如何处理安全对象的实现。

AOP联盟(MethodInvocation)安全拦截器

在 Spring Security 2.0之前,保护 MethodInvocation 实例需要大量的配置。现在,方法安全的推荐方法是使用 命名空间配置。这样一来,方法安全基础设施Bean就会自动为你配置,所以你不需要知道实现类。我们在这里只提供了对所涉及的类的一个快速概述。

方法的安全性是通过使用 MethodSecurityInterceptor 来执行的,它可以保护 MethodInvocation 实例。根据配置方式的不同,拦截器可能是特定于单个Bean的,也可能是多个Bean之间共享的。拦截器使用 MethodSecurityMetadataSource 实例来获取适用于特定方法调用的配置属性。 MapBasedMethodSecurityMetadataSource 用于存储由方法名(可以通配符)作为key的配置属性,当属性在应用上下文中使用 <intercept-methods><protect-point> 元素定义时,将在内部使用。其他实现被用来处理基于注解的配置。

明确的 MethodSecurityInterceptor Configuration

你可以直接在你的应用上下文中配置一个 MethodSecurityInterceptor,以便与Spring AOP的代理机制之一一起使用。

<bean id="bankManagerSecurity" class=
	"org.springframework.security.access.intercept.aopalliance.MethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
	<sec:method-security-metadata-source>
	<sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
	<sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
	</sec:method-security-metadata-source>
</property>
</bean>

AspectJ (JoinPoint) Security 拦截器

AspectJ的安全拦截器与上一节讨论的AOP联盟的安全拦截器非常相似。我们在本节中只讨论两者的区别。

AspectJ的拦截器被命名为 AspectJSecurityInterceptor。与AOP联盟的安全拦截器不同,AOP联盟的安全拦截器是依靠 Spring application context 通过代理来织入安全拦截器的,而 AspectJSecurityInterceptor 是通过 AspectJ 编译器织入的。在同一个应用程序中使用两种类型的安全拦截器并不罕见,AspectJSecurityInterceptor 用于域对象(domain object)实例安全,AOP 联盟 MethodSecurityInterceptor 用于服务层安全。

我们首先考虑 AspectJSecurityInterceptor 在Spring应用上下文中是如何配置的。

<bean id="bankManagerSecurity" class=
	"org.springframework.security.access.intercept.aspectj.AspectJMethodSecurityInterceptor">
<property name="authenticationManager" ref="authenticationManager"/>
<property name="accessDecisionManager" ref="accessDecisionManager"/>
<property name="afterInvocationManager" ref="afterInvocationManager"/>
<property name="securityMetadataSource">
	<sec:method-security-metadata-source>
	<sec:protect method="com.mycompany.BankManager.delete*" access="ROLE_SUPERVISOR"/>
	<sec:protect method="com.mycompany.BankManager.getBalance" access="ROLE_TELLER,ROLE_SUPERVISOR"/>
	</sec:method-security-metadata-source>
</property>
</bean>

这两个拦截器可以共享同一个 securityMetadataSource,因为 SecurityMetadataSource 的工作对象是 java.lang.reflect.Method 实例,而不是AOP库特定的类。你的访问决策可以访问相关的AOP库特定的调用(MethodInvocationJoinPoint),并且在做访问决策时可以考虑一系列额外的标准(如方法参数)。

接下来,你需要定义一个AspectJ 切面,如下面的例子所示。

package org.springframework.security.samples.aspectj;

import org.springframework.security.access.intercept.aspectj.AspectJSecurityInterceptor;
import org.springframework.security.access.intercept.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) {
			return proceed();
		}

		AspectJCallback callback = new AspectJCallback() {
			public Object proceedWithObject() {
				return proceed();
			}
		};

		return this.securityInterceptor.invoke(thisJoinPoint, callback);
	}

	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 表达)。对于那些好奇的人来说,AspectJCallback 是需要的,因为 proceed(); 语句只在 round() 体中有特殊意义。当 AspectJSecurityInterceptor 希望目标对象继续时,它就会调用这个匿名的 AspectJCallback 类。

你需要配置Spring来加载切面,并将其与 AspectJSecurityInterceptor 连接。下面的例子显示了实现这一目的的bean声明。

<bean id="domainObjectInstanceSecurityAspect"
	class="security.samples.aspectj.DomainObjectInstanceSecurityAspect"
	factory-method="aspectOf">
<property name="securityInterceptor" ref="bankManagerSecurity"/>
</bean>

现在你可以在你的应用程序中的任何地方创建你的Bean,使用任何你认为合适的方式(例如 new Person();),并且它们有 security 拦截器。