MethodValidationInterceptor、MethodSecurityInterceptor 切面顺序

203 阅读5分钟

问题描述

SpringBoot 项目引入了两个组件:Spring Web 和 Spring Security。

  • Spring Web:前端传入名为 viewId 的参数,使用 javax validation-api 的 @Validated + @NotBlank 注解进行非空校验。
  • Spring Security:使用 @PreAuthorize 注解和 SpEL 表达式进行鉴权:@PreAuthorize("hasViewPermission(#viewId)")。

但是,在自定义鉴权逻辑 hasViewPermission() 方法中,接收到的 viewId 参数为 null,导致系统抛出 NPE 异常。

问题解决

@NotBlank 注解会给 Controller 添加一个切面:MethodValidationInterceptor,@PreAuthorize 会给 Controller 添加一个切面:MethodSecurityInterceptor。好巧不巧,切面顺序刚好是这样的:MethodSecurityInterceptor --> MethodValidationInterceptor --> Controller --> MethodValidationInterceptor --> MethodSecurityInterceptor,切面顺序反掉了。正确的顺序应该是先执行参数校验,再执行权限校验,这样在 MethodSecurityInterceptor 中就不会出现 viewId == null 的情况了。

MethodValidationInterceptor 切面添加流程

FilteredMethodValidationPostProcessor,本质上是 BeanPostProcessor,给 Controller 添加了 MethodValidationInterceptor 切面,具体链路为:org.springframework.boot:spring-boot-autoconfigure:2.7.8 --> META-INF/spring/org.springframework.boot.autoconfigure.AutoConfiguration.imports --> org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration --> FilteredMethodValidationPostProcessor

在 org.springframework.boot:spring-boot-autoconfigure:2.7.8 包,META-INF/spring 目录,org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中,有一行配置:org.springframework.boot.autoconfigure.validation.ValidationAutoConfiguration,表示开启 ValidationAutoConfiguration 自动配置。

org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中的自动配置类是否生效,由 @EnableAutoConfiguration 注解决定。@EnableAutoConfiguration 注解标注了 @Import(AutoConfigurationImportSelector.class),AutoConfigurationImportSelector 负责导入 org.springframework.boot.autoconfigure.AutoConfiguration.imports 文件中的自动配置类。@EnableAutoConfiguration 注解是 @SpringBootApplication 的元注解,由 @SpringBootApplication 默认引入,导致我们在平常开发中会忽略该细节。

ValidationAutoConfiguration 自动配置类向 Spring 容器中注入了 FilteredMethodValidationPostProcessor 类型的 Bean。

@AutoConfiguration
@ConditionalOnClass(ExecutableValidator.class)
@ConditionalOnResource(resources = "classpath:META-INF/services/javax.validation.spi.ValidationProvider")
@Import(PrimaryDefaultValidatorPostProcessor.class)
public class ValidationAutoConfiguration {

    @Bean
    @ConditionalOnMissingBean(search = SearchStrategy.CURRENT)
    public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
           @Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
        FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(
              excludeFilters.orderedStream());
        boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
        processor.setProxyTargetClass(proxyTargetClass);
        processor.setValidator(validator);
        return processor;
    }

FilteredMethodValidationPostProcessor 的父类:MethodValidationPostProcessor#afterPropertiesSet() 中创建了 Advisor 对象,Advisor 包含两个部分:Pointcut 切入点表达式、Advice 切面逻辑。MethodValidationInterceptor 默认的切入点表达式为:标注 @Validated 注解的类。

public class MethodValidationPostProcessor extends AbstractBeanFactoryAwareAdvisingPostProcessor
		implements InitializingBean {
    @Override
    private Class<? extends Annotation> validatedAnnotationType = Validated.class;

    public void afterPropertiesSet() {
        Pointcut pointcut = new AnnotationMatchingPointcut(this.validatedAnnotationType, true);
        this.advisor = new DefaultPointcutAdvisor(pointcut, createMethodValidationAdvice(this.validator));
    }

    protected Advice createMethodValidationAdvice(@Nullable Validator validator) {
            return (validator != null ? new MethodValidationInterceptor(validator) : new MethodValidationInterceptor());
    }

MethodValidationPostProcessor 的父类:AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization() 方法使用了 Advisor 对象,先判断 bean 是否是 Advised 代理对象,如果是,就直接将 Advisor 放在合适的索引位置,不再创建代理对象(这样会造成代理对象嵌套)。如果还没有创建过代理对象,则调用 proxyFactory.getProxy(classLoader) 方法创建代理对象并返回。

public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
        if (this.advisor == null || bean instanceof AopInfrastructureBean) {
           // Ignore AOP infrastructure such as scoped proxies.
           return bean;
        }

        if (bean instanceof Advised) {
           Advised advised = (Advised) bean;
           if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
              // Add our local Advisor to the existing proxy's Advisor chain...
              if (this.beforeExistingAdvisors) {
                 advised.addAdvisor(0, this.advisor);
              }
              else {
                 advised.addAdvisor(this.advisor);
              }
              return bean;
           }
        }

        if (isEligible(bean, beanName)) {
           ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
           if (!proxyFactory.isProxyTargetClass()) {
              evaluateProxyInterfaces(bean.getClass(), proxyFactory);
           }
           proxyFactory.addAdvisor(this.advisor);
           customizeProxyFactory(proxyFactory);

           // Use original ClassLoader if bean class not locally loaded in overriding class loader
           ClassLoader classLoader = getProxyClassLoader();
           if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
              classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
           }
           return proxyFactory.getProxy(classLoader);
        }

        // No proxy needed.
        return bean;
    }

MethodSecurityInterceptor 切面添加流程

MethodSecurityMetadataSourceAdvisor,本质上是 Advisor,给 Controller 添加了 MethodValidationInterceptor 切面,具体链路为:@EnableGlobalMethodSecurity --> @Import({ GlobalMethodSecuritySelector.class }) --> MethodSecurityMetadataSourceAdvisorRegistrar --> MethodSecurityMetadataSourceAdvisor --> AnnotationAwareAspectJAutoProxyCreator。

@EnableGlobalMethodSecurity 注解标注了 @Import(GlobalMethodSecuritySelector.class),GlobalMethodSecuritySelector 向 Spring 容器中导入了 MethodSecurityMetadataSourceAdvisorRegistrar 类型的 Bean。

final class GlobalMethodSecuritySelector implements ImportSelector {

    @Override
    public String[] selectImports(AnnotationMetadata importingClassMetadata) {
       Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
       Map<String, Object> annotationAttributes = importingClassMetadata.getAnnotationAttributes(annoType.getName(),
             false);
       AnnotationAttributes attributes = AnnotationAttributes.fromMap(annotationAttributes);
       Assert.notNull(attributes, () -> String.format("@%s is not present on importing class '%s' as expected",
             annoType.getSimpleName(), importingClassMetadata.getClassName()));
       // TODO would be nice if could use BeanClassLoaderAware (does not work)
       Class<?> importingClass = ClassUtils.resolveClassName(importingClassMetadata.getClassName(),
             ClassUtils.getDefaultClassLoader());
       boolean skipMethodSecurityConfiguration = GlobalMethodSecurityConfiguration.class
             .isAssignableFrom(importingClass);
       AdviceMode mode = attributes.getEnum("mode");
       boolean isProxy = AdviceMode.PROXY == mode;
       String autoProxyClassName = isProxy ? AutoProxyRegistrar.class.getName()
             : GlobalMethodSecurityAspectJAutoProxyRegistrar.class.getName();
       boolean jsr250Enabled = attributes.getBoolean("jsr250Enabled");
       List<String> classNames = new ArrayList<>(4);
       if (isProxy) {
          classNames.add(MethodSecurityMetadataSourceAdvisorRegistrar.class.getName());
       }
       classNames.add(autoProxyClassName);
       if (!skipMethodSecurityConfiguration) {
          classNames.add(GlobalMethodSecurityConfiguration.class.getName());
       }
       if (jsr250Enabled) {
          classNames.add(Jsr250MetadataSourceConfiguration.class.getName());
       }
       return classNames.toArray(new String[0]);
    }

}

MethodSecurityMetadataSourceAdvisorRegistrar 负责向 Spring 容器中注册 MethodSecurityMetadataSourceAdvisor 类型的 Bean。

class MethodSecurityMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {

    @Override
    public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
       BeanDefinitionBuilder advisor = BeanDefinitionBuilder
             .rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
       advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
       advisor.addConstructorArgValue("methodSecurityInterceptor");
       advisor.addConstructorArgReference("methodSecurityMetadataSource");
       advisor.addConstructorArgValue("methodSecurityMetadataSource");
       MultiValueMap<String, Object> attributes = importingClassMetadata
             .getAllAnnotationAttributes(EnableGlobalMethodSecurity.class.getName());
       Integer order = (Integer) attributes.getFirst("order");
       if (order != null) {
          advisor.addPropertyValue("order", order);
       }
       registry.registerBeanDefinition("metaDataSourceAdvisor", advisor.getBeanDefinition());
    }

}

在 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization() 方法,进入到父类 AbstractAutoProxyCreator#postProcessAfterInitialization() 方法中,会调用 wrapIfNecessary() 方法,从 Spring 容器中取出 Eligible Advisors,给 target bean 创建代理对象。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
                
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
           Object cacheKey = getCacheKey(bean.getClass(), beanName);
           if (this.earlyProxyReferences.remove(cacheKey) != bean) {
              return wrapIfNecessary(bean, beanName, cacheKey);
           }
        }
        return bean;
    }

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
           return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
           return bean;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
           this.advisedBeans.put(cacheKey, Boolean.FALSE);
           return bean;
        }

        // Create proxy if we have advice.
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
           this.advisedBeans.put(cacheKey, Boolean.TRUE);
           Object proxy = createProxy(
                 bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
           this.proxyTypes.put(cacheKey, proxy.getClass());
           return proxy;
        }

        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

Spring 中创建代理的两种方式

ProxyProcessorSupport 作为 proxy processors 的公共父类:Base class with common functionality for proxy processors,衍生出了两个抽象子类:AbstractAdvisingBeanPostProcessor 和 AbstractAutoProxyCreator。

  • AbstractAdvisingBeanPostProcessor:在 AbstractAdvisingBeanPostProcessor 显示创建 Advisor 对象,一个 AbstractAdvisingBeanPostProcessor 对应着一个 Advisor。
  • AbstractAutoProxyCreator:将 Advisor 对象注册为 Spring Bean,AbstractAutoProxyCreator 可以自动将这些 Advisors 作用到对应的 target bean 上,一个 AbstractAutoProxyCreator 可对应多个 Advisors。

AbstractAdvisingBeanPostProcessor

AbstractAdvisingBeanPostProcessor 抽象父类中有一个成员变量:protected Advisor advisor,proxy processor 和 advisor 是一一对应的关系,advisor 和注解一般也是一一对应的关系。典型的例子有: MethodValidationPostProcessor 对应着 @Validated 注解,AsyncAnnotationBeanPostProcessor 对应着 @Async 注解。

AbstractAdvisingBeanPostProcessor#postProcessAfterInitialization():可以在 Bean 为代理对象的基础上,继续向 Bean 添加 Advisor,可以通过 beforeExistingAdvisors 参数,设置新添加 Advisor 的顺序,是在最后面添加 advised.addAdvisor(this.advisor),还是在最前面添加 advised.addAdvisor(0, this.advisor)。

public abstract class AbstractAdvisingBeanPostProcessor extends ProxyProcessorSupport implements BeanPostProcessor {

    @Nullable
    protected Advisor advisor;

    protected boolean beforeExistingAdvisors = false;

    private final Map<Class<?>, Boolean> eligibleBeans = new ConcurrentHashMap<>(256);


    // Set whether this post-processor's advisor is supposed to apply before existing advisors when encountering a pre-advised object.
    // Default is "false", applying the advisor after existing advisors, i.e. as close as possible to the target method. Switch this to "true" in order for this post-processor's advisor to wrap existing advisors as well.
    public void setBeforeExistingAdvisors(boolean beforeExistingAdvisors) {
       this.beforeExistingAdvisors = beforeExistingAdvisors;
    }

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) {
       if (this.advisor == null || bean instanceof AopInfrastructureBean) {
          // Ignore AOP infrastructure such as scoped proxies.
          return bean;
       }

       if (bean instanceof Advised) {
          Advised advised = (Advised) bean;
          if (!advised.isFrozen() && isEligible(AopUtils.getTargetClass(bean))) {
             // Add our local Advisor to the existing proxy's Advisor chain...
             if (this.beforeExistingAdvisors) {
                advised.addAdvisor(0, this.advisor);
             }
             else {
                advised.addAdvisor(this.advisor);
             }
             return bean;
          }
       }

       if (isEligible(bean, beanName)) {
          ProxyFactory proxyFactory = prepareProxyFactory(bean, beanName);
          if (!proxyFactory.isProxyTargetClass()) {
             evaluateProxyInterfaces(bean.getClass(), proxyFactory);
          }
          proxyFactory.addAdvisor(this.advisor);
          customizeProxyFactory(proxyFactory);

          // Use original ClassLoader if bean class not locally loaded in overriding class loader
          ClassLoader classLoader = getProxyClassLoader();
          if (classLoader instanceof SmartClassLoader && classLoader != bean.getClass().getClassLoader()) {
             classLoader = ((SmartClassLoader) classLoader).getOriginalClassLoader();
          }
          return proxyFactory.getProxy(classLoader);
       }

       // No proxy needed.
       return bean;
    }

AbstractAutoProxyCreator

InfrastructureAdvisorAutoProxyCreator 作为 AbstractAutoProxyCreator 的实现类,一般通过 @EnableGlobalMethodSecurity,@EnableTransactionManagement 这类注解注册到 Spring 容器中。以 @EnableGlobalMethodSecurity 注解为例,调用链路为 @EnableGlobalMethodSecurity --> GlobalMethodSecuritySelector --> AutoProxyRegistrar --> AopConfigUtils#registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source)。

但 SpringBoot 自动配置类 AopAutoConfiguration 中会开启 @EnableAspectJAutoProxy,该注解会引入 AbstractAutoProxyCreator 的另一个实现类:AnnotationAwareAspectJAutoProxyCreator。该类有优先级会比 InfrastructureAdvisorAutoProxyCreator 高,会直接替换掉 InfrastructureAdvisorAutoProxyCreator 类型的 Bean,该 bean name 为:org.springframework.aop.config.internalAutoProxyCreator。调用链路 @EnableAspectJAutoProxy --> AspectJAutoProxyRegistrar --> AopConfigUtils#registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source)。

public abstract class AopConfigUtils {

    /**
     * The bean name of the internally managed auto-proxy creator.
     */
    public static final String AUTO_PROXY_CREATOR_BEAN_NAME =
                    "org.springframework.aop.config.internalAutoProxyCreator";


    /**
     * Stores the auto proxy creator classes in escalation order.
     */
    private static final List<Class<?>> APC_PRIORITY_LIST = new ArrayList<>(3);

    static {
            // Set up the escalation list...
            APC_PRIORITY_LIST.add(InfrastructureAdvisorAutoProxyCreator.class);
            APC_PRIORITY_LIST.add(AspectJAwareAdvisorAutoProxyCreator.class);
            APC_PRIORITY_LIST.add(AnnotationAwareAspectJAutoProxyCreator.class);
    }


    @Nullable
    public static BeanDefinition registerAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
            return registerAutoProxyCreatorIfNecessary(registry, null);
    }

    @Nullable
    public static BeanDefinition registerAutoProxyCreatorIfNecessary(
                    BeanDefinitionRegistry registry, @Nullable Object source) {

            return registerOrEscalateApcAsRequired(InfrastructureAdvisorAutoProxyCreator.class, registry, source);
    }

    @Nullable
    public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
            return registerAspectJAutoProxyCreatorIfNecessary(registry, null);
    }

    @Nullable
    public static BeanDefinition registerAspectJAutoProxyCreatorIfNecessary(
                    BeanDefinitionRegistry registry, @Nullable Object source) {

            return registerOrEscalateApcAsRequired(AspectJAwareAdvisorAutoProxyCreator.class, registry, source);
    }

    @Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(BeanDefinitionRegistry registry) {
            return registerAspectJAnnotationAutoProxyCreatorIfNecessary(registry, null);
    }

    @Nullable
    public static BeanDefinition registerAspectJAnnotationAutoProxyCreatorIfNecessary(
                    BeanDefinitionRegistry registry, @Nullable Object source) {

            return registerOrEscalateApcAsRequired(AnnotationAwareAspectJAutoProxyCreator.class, registry, source);
    }


    @Nullable
    private static BeanDefinition registerOrEscalateApcAsRequired(
           Class<?> cls, BeanDefinitionRegistry registry, @Nullable Object source) {

        Assert.notNull(registry, "BeanDefinitionRegistry must not be null");

        if (registry.containsBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME)) {
           BeanDefinition apcDefinition = registry.getBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME);
           if (!cls.getName().equals(apcDefinition.getBeanClassName())) {
              int currentPriority = findPriorityForClass(apcDefinition.getBeanClassName());
              int requiredPriority = findPriorityForClass(cls);
              if (currentPriority < requiredPriority) {
                 apcDefinition.setBeanClassName(cls.getName());
              }
           }
           return null;
        }

        RootBeanDefinition beanDefinition = new RootBeanDefinition(cls);
        beanDefinition.setSource(source);
        beanDefinition.getPropertyValues().add("order", Ordered.HIGHEST_PRECEDENCE);
        beanDefinition.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);
        registry.registerBeanDefinition(AUTO_PROXY_CREATOR_BEAN_NAME, beanDefinition);
        return beanDefinition;
    }

AbstractAutoProxyCreator#wrapIfNecessary() 方法中会取出 Spring 容器符合条件的 Advisors,并作用到 target bean 上。

public abstract class AbstractAutoProxyCreator extends ProxyProcessorSupport
		implements SmartInstantiationAwareBeanPostProcessor, BeanFactoryAware {
                
    @Override
    public Object postProcessAfterInitialization(@Nullable Object bean, String beanName) {
        if (bean != null) {
           Object cacheKey = getCacheKey(bean.getClass(), beanName);
           if (this.earlyProxyReferences.remove(cacheKey) != bean) {
              return wrapIfNecessary(bean, beanName, cacheKey);
           }
        }
        return bean;
    }

    protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) {
        if (StringUtils.hasLength(beanName) && this.targetSourcedBeans.contains(beanName)) {
           return bean;
        }
        if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) {
           return bean;
        }
        if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) {
           this.advisedBeans.put(cacheKey, Boolean.FALSE);
           return bean;
        }

        // Create proxy if we have advice.
        Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null);
        if (specificInterceptors != DO_NOT_PROXY) {
           this.advisedBeans.put(cacheKey, Boolean.TRUE);
           Object proxy = createProxy(
                 bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean));
           this.proxyTypes.put(cacheKey, proxy.getClass());
           return proxy;
        }

        this.advisedBeans.put(cacheKey, Boolean.FALSE);
        return bean;
    }

解决方案:setBeforeExistingAdvisors(true)

BeanPostProcessors BeforeInitialization 的处理时机:AbstractAutowireCapableBeanFactory#applyBeanPostProcessorsBeforeInitialization() 方法。在没有显示设置 order 的情况下。AnnotationAwareAspectJAutoProxyCreator 在 FilteredMethodValidationPostProcessor 前面。AnnotationAwareAspectJAutoProxyCreator 默认 order 为 Integer.MIN_VALUE(最高优先级,Spring 的默认行为,不要去动他),FilteredMethodValidationPostProcessor 默认 order 为 Integer.MAX_VALUE(最低优先级)。

image.png

原因分析:先执行 AnnotationAwareAspectJAutoProxyCreator#postProcessAfterInitialization(),创建了 target bean 的代理对象,再执行 FilteredMethodValidationPostProcessor#postProcessAfterInitialization(),将 Method Validation Advisor 添加到 MethodSecurityMetadataSourceAdvisor 之后,对应的切面顺序为:MethodSecurityInterceptor --> MethodValidationInterceptor,与预期相反。

解决办法:在 ValidationAutoConfiguration#methodValidationPostProcessor() 的配置基础上,加一行代码:processor.setBeforeExistingAdvisors(true),将 Method Validation 对应的 Advisor 提到所有 Advisor 的最前面。ValidationAutoConfiguration#methodValidationPostProcessor() 上标注了 @ConditionalOnMissingBean(search = SearchStrategy.CURRENT),如果当前 Spring 容器中已经有此类型的 Bean,自动配置方法就不会生效了。

@Bean
public static MethodValidationPostProcessor methodValidationPostProcessor(Environment environment,
                                                                          @Lazy Validator validator, ObjectProvider<MethodValidationExcludeFilter> excludeFilters) {
    FilteredMethodValidationPostProcessor processor = new FilteredMethodValidationPostProcessor(
            excludeFilters.orderedStream());
    boolean proxyTargetClass = environment.getProperty("spring.aop.proxy-target-class", Boolean.class, true);
    processor.setBeforeExistingAdvisors(true);
    processor.setProxyTargetClass(proxyTargetClass);
    processor.setValidator(validator);
    return processor;
}