前言
- 想要在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](https://static.blogweb.cn/article/5ba82aa71a774ea0b3cb6d2e356eb352.webp)
- 我们发现此类实现了: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原来就支持动态代理,所以这个类并不仅仅只有SpringSecurity注册了的,比如说
- Spring中的缓存机制
- Spring中的事务机制
- SpringBoot的AopAutoConfiguration中
![[SpringSecurity5.6.2源码分析二十六]:@EnableGlobalMethodSecurity](https://static.blogweb.cn/article/f48a6550f1584bf6ab2e587944e6f202.webp)
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());
}
}
/**
* 针对三种类型的权限注解进行匹配,本质上是一个 {@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](https://static.blogweb.cn/article/81fdae1001cf4f3b90290c4591fb3e19.webp)
- 可以看出刚好有三种权限注解对应的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](https://static.blogweb.cn/article/b7a3f1fd47c54501a8fb256091ddc768.webp)
6. GlobalMethodSecurityConfiguration
- 此类比较复杂,也是涉及到最终的MethodSecurityInterceptor,所以另开一章讲