likes
comments
collection
share

[SpringSecurity5.6.2源码分析二十六]:@EnableGlobalMethodSecurity

作者站长头像
站长
· 阅读数 59

前言

  • 想要在SpringSecurity中进行权限校验,我们可以使用HttpSecurity进行配置
  • 当然SpringSecurity中也可以通过注解来实现,比如@PreAuthorize,@Secured等等
  • 但是这不是默认开启的,需要在带有@Configuration的类上加入@EnableGlobalMethodSecurity才可以
@EnableGlobalMethodSecurity(prePostEnabled = true, jsr250Enabled = true, securedEnabled = true)
  • 注意:权限注解的原理是基于动态代理

1. @EnableGlobalMethodSecurity

  • @EnableGlobalMethodSecurity是用于开启方法权限校验的
...
@Import({ GlobalMethodSecuritySelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {

   /**
    * 是否开启Pre Post注解
    * <li>{@link org.springframework.security.access.prepost.PreAuthorize @PreAuthorize},
    * {@link org.springframework.security.access.prepost.PostAuthorize @PostAuthorize},
    * {@link org.springframework.security.access.prepost.PostFilter @PostFilter},
    * {@link org.springframework.security.access.prepost.PreFilter @PreFilter}</li>
    * @return true if pre post annotations should be enabled false otherwise.
    */
   boolean prePostEnabled() default false;

   /**
    * 是否开启{@link Secured @Secured}
    */
   boolean securedEnabled() default false;

   /**
    * 启用 JSR-250 注解
    * <li>{@link javax.annotation.security.RolesAllowed @RolesAllowed}.
    * {@link javax.annotation.security.PermitAll @RolesAllowed},
    * {@link javax.annotation.security.DenyAll @RolesAllowed}</li>
    * @return true if JSR-250 should be enabled false otherwise.
    */
   boolean jsr250Enabled() default false;

   /**
    * <ul>
    *     <li>True:直接使用Cglib代理目标类</li>
    *     <li>False:如果目标类有或者就是是一个接口,使用JDK,否则使用Cglib</li>
    *     <li>由于这个值后面会直接设置到{@link org.springframework.aop.framework.autoproxy.InfrastructureAdvisorAutoProxyCreator InfrastructureAdvisorAutoProxyCreator}
    *     所有这将影响到所有Spring管理的Bean,比如@Transactional</li>
    * </ul>
    */
   boolean proxyTargetClass() default false;

   AdviceMode mode() default AdviceMode.PROXY;

   int order() default Ordered.LOWEST_PRECEDENCE;

}
  • 首先分析源码可以知道此注解可以开启三种不同的权限注解
    • SpringSecurity(pre post)
      • @PreAuthorize:在方法执行前调用,判断是否有指定权限
      • @PostAuthorize:在方法执行后调用,可以用来判断权限,或者对返回值判断
      • @PreFilter:在方法执行前被调用:对于入参中的集合进行过滤,不符合的将会被删除
      • @PostFilter:和PreFilter 很像,不过是在方法执行后,对于返回值进行过滤
    • @Secured:
    • JSR-250:
      • @RolesAllowed:
      • @PermitAll:
      • @DenyAll:
  • 其次是这个注解导入了一个选择器:GlobalMethodSecuritySelector

2. GlobalMethodSecuritySelector

  • 此选择器的作用是确定有哪些注解类型要开启并注册其配置类
final class GlobalMethodSecuritySelector implements ImportSelector {

   @Override
   public String[] selectImports(AnnotationMetadata importingClassMetadata) {
      Class<EnableGlobalMethodSecurity> annoType = EnableGlobalMethodSecurity.class;
      // 获得导入类上有关EnableGlobalMethodSecurity的属性
      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.PROXY
      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);

      // 默认就是True
      if (isProxy) {
         // 重点:注册一个Advisor
         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
    • AutoProxyRegistrar
    • GlobalMethodSecurityConfiguration
    • Jsr250MetadataSourceConfiguration

3. AutoProxyRegistrar

  • 介绍这个类之前要先介绍此类实现的接口:ImportBeanDefinitionRegistrar
  • ImportBeanDefinitionRegistrar:可以动态的往Bean工厂中注册BeanDefinitions,而不像@Component
  • 然后再看AutoProxyRegistrar的源码
public class AutoProxyRegistrar implements ImportBeanDefinitionRegistrar {

   private final Log logger = LogFactory.getLog(getClass());

   /**
    * 注册InfrastructureAdvisorAutoProxyCreator,然后设置其属性
    */
   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      boolean candidateFound = false;
      // 获取导入类上的注解
      Set<String> annTypes = importingClassMetadata.getAnnotationTypes();
      for (String annType : annTypes) {
         // 获得指定注解
         AnnotationAttributes candidate = AnnotationConfigUtils.attributesFor(importingClassMetadata, annType);
         if (candidate == null) {
            continue;
         }
         Object mode = candidate.get("mode");
         Object proxyTargetClass = candidate.get("proxyTargetClass");
         // 如果设置了AdviceMode和ProxyTargetClass
         if (mode != null && proxyTargetClass != null && AdviceMode.class == mode.getClass() &&
               Boolean.class == proxyTargetClass.getClass()) {
            candidateFound = true;
            // 注册自动代理创建器
            if (mode == AdviceMode.PROXY) {
               AopConfigUtils.registerAutoProxyCreatorIfNecessary(registry);
               // 是否强制使用Cglib
               if ((Boolean) proxyTargetClass) {
                  AopConfigUtils.forceAutoProxyCreatorToUseClassProxying(registry);
                  return;
               }
            }
         }
      }
      ...
    }
}
  • registerBeanDefinitions(...)方法的重点就是第27行,这里注册了一个InfrastructureAdvisorAutoProxyCreator
  • 要理解为什么注册这个类,要先看此类的结构 [SpringSecurity5.6.2源码分析二十六]:@EnableGlobalMethodSecurity
  • 我们发现此类实现了:BeanPostProcessor,这个类Spring提供的,作用就是在Bean初始化前后调用回调方法
public interface BeanPostProcessor {

   /**
    * 执行初始化方法之前调用
    */
   @Nullable
   default Object postProcessBeforeInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }

   /**
    * 执行初始化方法之后调用
    */
   @Nullable
   default Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
      return bean;
   }

}
  • 然后再看InfrastructureAdvisorAutoProxyCreator是如何实现这两个方法的呢
@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;
}
  • 这里是根据Advice和Advisor确定是否需要创建代理对象
    • 一旦被代理,存入Spring容器中的就是代理对象
  • Spring原来就支持动态代理,所以这个类并不仅仅只有SpringSecurity注册了的,比如说
    • Spring中的缓存机制
    • Spring中的事务机制
    • SpringBoot的AopAutoConfiguration中 [SpringSecurity5.6.2源码分析二十六]:@EnableGlobalMethodSecurity

4. MethodSecurityMetadataSourceAdvisorRegistrar

  • 上一小结介绍的ImportBeanDefinitionRegistrar是依靠注册的Advice和Advisor确定是否需要创建代理对象
  • 自然而然SpringSecurity就有一个Advice或者Advisor去判断方法是否需要被代理
  • 而MethodSecurityMetadataSourceAdvisorRegistrar正是为其注册Advisor
class MethodSecurityMetadataSourceAdvisorRegistrar implements ImportBeanDefinitionRegistrar {

   @Override
   public void registerBeanDefinitions(AnnotationMetadata importingClassMetadata, BeanDefinitionRegistry registry) {
      // 拿到Advice
      BeanDefinitionBuilder advisor = BeanDefinitionBuilder
            .rootBeanDefinition(MethodSecurityMetadataSourceAdvisor.class);
      // 设置此Bean的角色
      advisor.setRole(BeanDefinition.ROLE_INFRASTRUCTURE);

      // 设置构造方法参数列表
      // 详情见MethodSecurityMetadataSourceAdvisor的构造方法
      advisor.addConstructorArgValue("methodSecurityInterceptor");
      // 这个指的是第二个参数必须是这个类型的
      advisor.addConstructorArgReference("methodSecurityMetadataSource");
      advisor.addConstructorArgValue("methodSecurityMetadataSource");

      // 设置Bean实例化的顺序
      MultiValueMap<String, Object> attributes = importingClassMetadata
            .getAllAnnotationAttributes(EnableGlobalMethodSecurity.class.getName());
      Integer order = (Integer) attributes.getFirst("order");
      if (order != null) {
         advisor.addPropertyValue("order", order);
      }

      // 往Bean工厂中注册此BeanDefinition
      registry.registerBeanDefinition("metaDataSourceAdvisor", advisor.getBeanDefinition());
   }

}
  • 而注册的这个Advisor的重点在于其一个内部类
/**
 * 针对三种类型的权限注解进行匹配,本质上是一个 {@link org.springframework.aop.MethodMatcher MethodMatcher}
 */
class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut implements Serializable {

   @Override
   public boolean matches(Method m, Class<?> targetClass) {
      MethodSecurityMetadataSource source = MethodSecurityMetadataSourceAdvisor.this.attributeSource;
      return !CollectionUtils.isEmpty(source.getAttributes(m, targetClass));
   }

}
  • 这里内部类就是用于判断传入类的方法是否需要被代理 [SpringSecurity5.6.2源码分析二十六]:@EnableGlobalMethodSecurity
  • 可以看出刚好有三种权限注解对应的MethodSecurityMetadataSource,这里我们只看下PrePostAnnotationSecurityMetadataSource
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
   if (method.getDeclaringClass() == Object.class) {
      return Collections.emptyList();
   }
   // 在方法和方法的声明类上查找下面四种指定注解
   PreFilter preFilter = findAnnotation(method, targetClass, PreFilter.class);
   PreAuthorize preAuthorize = findAnnotation(method, targetClass, PreAuthorize.class);
   PostFilter postFilter = findAnnotation(method, targetClass, PostFilter.class);
   // TODO: 由于@PostAuthorize可以针对于返回值,所以可以在这里检查空返回值,然后抛出异常
   PostAuthorize postAuthorize = findAnnotation(method, targetClass, PostAuthorize.class);

   // 方法上没有这四种注解,直接返回空集合
   if (preFilter == null && preAuthorize == null && postFilter == null && postAuthorize == null) {
      // There is no meta-data so return
      return Collections.emptyList();
   }

   // 获得这些注解的表达式
   String preFilterAttribute = (preFilter != null) ? preFilter.value() : null;
   String filterObject = (preFilter != null) ? preFilter.filterTarget() : null;
   String preAuthorizeAttribute = (preAuthorize != null) ? preAuthorize.value() : null;
   String postFilterAttribute = (postFilter != null) ? postFilter.value() : null;
   String postAuthorizeAttribute = (postAuthorize != null) ? postAuthorize.value() : null;


   ArrayList<ConfigAttribute> attrs = new ArrayList<>(2);
   // 创建方法执行前需要判断的表达式
   PreInvocationAttribute pre = this.attributeFactory.createPreInvocationAttribute(preFilterAttribute,
         filterObject, preAuthorizeAttribute);
   if (pre != null) {
      attrs.add(pre);
   }

   // 创建方法执行后需要判断的表达式
   PostInvocationAttribute post = this.attributeFactory.createPostInvocationAttribute(postFilterAttribute,
         postAuthorizeAttribute);
   if (post != null) {
      attrs.add(post);
   }
   attrs.trimToSize();
   return attrs;
}
  • 很明显就是判断是否带有指定注解而已,一旦有指定的注解就会为其创建代理对象

5. Jsr250MetadataSourceConfiguration

  • Jsr250MetadataSourceConfiguration就比较简单了,就注册了一个Jsr250MethodSecurityMetadataSource
@Configuration(proxyBeanMethods = false)
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
class Jsr250MetadataSourceConfiguration {

   @Bean
   @Role(BeanDefinition.ROLE_INFRASTRUCTURE)
   Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource() {
      return new Jsr250MethodSecurityMetadataSource();
   }

}
  • 看下Jsr250MethodSecurityMetadataSource的结构,发现它正是一个MethodSecurityMetadataSource,这也是MethodSecurityMetadataSourcePointcut中MethodSecurityMetadataSource的来源之一 [SpringSecurity5.6.2源码分析二十六]:@EnableGlobalMethodSecurity

6. GlobalMethodSecurityConfiguration

  • 此类比较复杂,也是涉及到最终的MethodSecurityInterceptor,所以另开一章讲
转载自:https://juejin.cn/post/7276113864122105919
评论
请登录