Spring Security 安全对象实现

1. 前言

上节我们讨论了如何利用表达式简化权限规则的配置,本节将讨论对象级的权限管理。

安全对象(Security Object)指任何可以有安全限制的对象,常见的比如方法调用,或者 Web 请求等。

每一个安全对象都有自己的一个拦截器实现。

图片描述

本节,我们将讨论安全对象的鉴权如何实现。

2. AOP 安全拦截器

在 Spring Security 2.0 之前,想要对「安全对象」进行保护,需要使用大量的「样板模式」来实现。

所谓样板模式,就是在父类(抽象类)中公开定义某对象的执行方法,该方法可以被其子类重写,但是该方法的调用需要在父类的方法中完成,也就是由父类决定行为,由子类决定过程细节,封装不变的部分,扩展可变的部分。

在新版本的 Spring Security 中,采用了「基于命名空间配置」的方式实现安全保护,通过这种方式,Bean 对象可以自动配置出安全策略,而不必过多关注细节。

这其中主要涉及了以下一些类:

方法的安全性通过 MethodSecurityInterceptor 实现,用来保护方法执行过程的安全。拦截器可以配置为指定单一的 Bean 对象或者在多个 Bean 对象之间共享,它使用 MethodSecurityMetadataSource 实例里维护配置属性,用来应用到一个具体的方法调用上。MapBasedMethodSecurityMetadataSource 实例用于储存那些以方法名为主键的配置属性,并且当属性在应用上下文中被用于 <intercept-methods><protect-point> 元素时会在内部直接调用这些属性。其他实现则基于注释的配置。

我们可以在应用上下文中显示的配置 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>

3. AspectJ 安全拦截器

AspectJ 安全拦截器和 AOP 联盟安全拦截器类似,但仍有一些不同。

AspectJ 安全拦截器的应用类名为 AspectJSecurityInterceptor。不同于 AOP 联盟安全拦截器,它不是基于 Spring 应用上下文来激活拦截器,它通过 AspectJ 编译器实现。多数情况下,同一应用会出现这两种安全拦截器,AspectJ 用于域对象的安全控制,AOP 联盟安全拦截器用于服务层的安全。

AspectJSecurityInterceptor 的配置方式如下:

<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>

可见,除了类名之外,AspectJ 方式与 AOP 联盟方式配置几乎一样。不仅如此,这两个拦截器可以共用 securityMetadataSource 对象。

下一步,我们需要定义 AspectJ 的 aspect,例如:

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 实例。AspectJCallback 被用于执行 proceed() ,该调用只有在 around() 方法中才能得以执行,当我们需要目标对象继续执行时,这些匿名的回调函数会被调用。

下一步,我们需要配置 Spring 加载 aspect 并关联到 AspectJSecurityInterceptor 拦截器中,如下:

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

到这里为止,我们可以随意的创建自己的 bean 对象了,他们都将被安全拦截器覆盖。

4. 小结

本节讨论了对象级的安全配置策略,主要内容有:

  • Spring Security 中的安全对象指所有需要被安全限制的对象;
  • Spring Security 通过配置拦截器的方式保护安全对象不受非法访问;
  • Spring Security 的安全对象拦截器分为 AOP 方式和 AspectJ 方式,两种方式的执行时期不同,前置在程序运行过程中动态启用,后者在编译时静态启用;
  • 两种拦截器并不影响,通常可以一起使用。

下节我们讨论「域对象」的权限配置。