问题描述
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(最低优先级)。
原因分析:先执行 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;
}