1.GlobalMethodSecurityConfiguration
- GlobalMethodSecurityConfiguration的核心是为了注册MethodSecurityInterceptor
- MethodSecurityInterceptor :是最终注册到代理类(DynamicAdvisedInterceptor )中的拦截器,会在被代理方法执行前后校验权限,支持的注解如下:
- @PreAuthorize:在方法执行前调用,判断是否有指定权限
- @PostAuthorize:在方法执行后调用,可以用来判断权限,或者对返回值判断
- @PreFilter:在方法执行前被调用:对于入参中的集合进行过滤,不符合的将会被删除
- @PostFilter:和PreFilter 很像,不过是在方法执行后,对于返回值进行过滤
- 下图就是此配置类注册的核心类
- 接下来先介绍此配置类的重要方法
1.1 afterInvocationManager()
- afterInvocationManager():创建执行后管理器(AfterInvocationManager)
java
复制代码
protected AfterInvocationManager afterInvocationManager() {
if (prePostEnabled()) {
AfterInvocationProviderManager invocationProviderManager = new AfterInvocationProviderManager();
ExpressionBasedPostInvocationAdvice postAdvice = new ExpressionBasedPostInvocationAdvice(
getExpressionHandler());
PostInvocationAdviceProvider postInvocationAdviceProvider = new PostInvocationAdviceProvider(postAdvice);
List<AfterInvocationProvider> afterInvocationProviders = new ArrayList<>();
afterInvocationProviders.add(postInvocationAdviceProvider);
invocationProviderManager.setProviders(afterInvocationProviders);
return invocationProviderManager;
}
return null;
}
- AfterInvocationManager:当目标方法上带有@PostAuthorize和@PostFilter并且目标方法执行完毕后,开始校验权限和过滤返回值
java
复制代码
public interface AfterInvocationManager {
/**
* 根据处理方法执行后权限表达式和返回值进行操作
*/
Object decide(Authentication authentication, Object object, Collection<ConfigAttribute> attributes,
Object returnedObject) throws AccessDeniedException;
/**
* 此 {@link AfterInvocationManager} 是否支持解析 {@link ConfigAttribute}
*/
boolean supports(ConfigAttribute attribute);
boolean supports(Class<?> clazz);
}
1.2 accessDecisionManager()
- accessDecisionManager():创建访问决策管理器
java
复制代码
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<?>> decisionVoters = new ArrayList<>();
// 开启了prePost的权限注解,创建对应的访问决策投票器
if (prePostEnabled()) {
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
decisionVoters.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
}
// 开启了jsr250的权限注解,创建对应的访问决策投票器
if (jsr250Enabled()) {
decisionVoters.add(new Jsr250Voter());
}
RoleVoter roleVoter = new RoleVoter();
// 获得角色前缀
GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
if (grantedAuthorityDefaults != null) {
roleVoter.setRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
// 有任何一个角色匹配就投同意票
decisionVoters.add(roleVoter);
// 根据认证方式投票
decisionVoters.add(new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
1.3 authenticationManager()
- authenticationManager():创建认证管理器
java
复制代码
protected AuthenticationManager authenticationManager() throws Exception {
if (this.authenticationManager == null) {
DefaultAuthenticationEventPublisher eventPublisher = this.objectPostProcessor
.postProcess(new DefaultAuthenticationEventPublisher());
this.auth = new AuthenticationManagerBuilder(this.objectPostProcessor);
this.auth.authenticationEventPublisher(eventPublisher);
configure(this.auth);
// 是使用容器中的还是用构建起创建出来的
this.authenticationManager = (this.disableAuthenticationRegistry)
? getAuthenticationConfiguration().getAuthenticationManager() : this.auth.build();
}
return this.authenticationManager;
}
- methodSecurityMetadataSource():注册安全元数据源
java
复制代码
@Bean
@Role(BeanDefinition.ROLE_INFRASTRUCTURE)
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
List<MethodSecurityMetadataSource> sources = new ArrayList<>();
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
getExpressionHandler());
// 自定义安全元数据源
MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
if (customMethodSecurityMetadataSource != null) {
sources.add(customMethodSecurityMetadataSource);
}
boolean hasCustom = customMethodSecurityMetadataSource != null;
// 是否开启了下面三种类型的注解
boolean isPrePostEnabled = prePostEnabled();
boolean isSecuredEnabled = securedEnabled();
boolean isJsr250Enabled = jsr250Enabled();
Assert.state(isPrePostEnabled || isSecuredEnabled || isJsr250Enabled || hasCustom,
"In the composition of all global method configuration, "
+ "no annotation support was actually activated");
// 尝试添加下面三种安全元数据源
if (isPrePostEnabled) {
sources.add(new PrePostAnnotationSecurityMetadataSource(attributeFactory));
}
if (isSecuredEnabled) {
sources.add(new SecuredAnnotationSecurityMetadataSource());
}
if (isJsr250Enabled) {
GrantedAuthorityDefaults grantedAuthorityDefaults = getSingleBeanOrNull(GrantedAuthorityDefaults.class);
Jsr250MethodSecurityMetadataSource jsr250MethodSecurityMetadataSource = this.context
.getBean(Jsr250MethodSecurityMetadataSource.class);
// 设置角色前缀
if (grantedAuthorityDefaults != null) {
jsr250MethodSecurityMetadataSource.setDefaultRolePrefix(grantedAuthorityDefaults.getRolePrefix());
}
sources.add(jsr250MethodSecurityMetadataSource);
}
return new DelegatingMethodSecurityMetadataSource(sources);
}
1.5 preInvocationAuthorizationAdvice()
- preInvocationAuthorizationAdvice():注册PreInvocationAuthorizationAdvice
java
复制代码
@Bean
public PreInvocationAuthorizationAdvice preInvocationAuthorizationAdvice() {
ExpressionBasedPreInvocationAdvice preInvocationAdvice = new ExpressionBasedPreInvocationAdvice();
preInvocationAdvice.setExpressionHandler(getExpressionHandler());
return preInvocationAdvice;
}
- PreInvocationAuthorizationAdvice:在调用处理方法之前调用,进行参数过滤和权限判断,是在访问决策投票器(PreInvocationAuthorizationAdviceVoter)中被执行的,通过此Advice决定此投票器的最终结果
1.7 methodSecurityInterceptor()
- methodSecurityInterceptor():注册 MethodSecurityInterceptor
java
复制代码
@Bean
public MethodInterceptor methodSecurityInterceptor(MethodSecurityMetadataSource methodSecurityMetadataSource) {
this.methodSecurityInterceptor = isAspectJ() ? new AspectJMethodSecurityInterceptor()
: new MethodSecurityInterceptor();
//设置访问决策管理器
this.methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
//设置执行后管理器
this.methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager());
//设置安全元数据源
this.methodSecurityInterceptor.setSecurityMetadataSource(methodSecurityMetadataSource);
RunAsManager runAsManager = runAsManager();
if (runAsManager != null) {
this.methodSecurityInterceptor.setRunAsManager(runAsManager);
}
return this.methodSecurityInterceptor;
}
2. 基于权限注解如何创建代理对象
- 虽然SpringSecurity通过AutoProxyRegistrar往容器注册了一个InfrastructureAdvisorAutoProxyCreator对应的RootBeanDefinition,但实际上注册到容器中的Bean却是AnnotationAwareAspectJAutoProxyCreator
- 这是因为我们注册Bean的名称是下面这个,但是SpringBoot在运行过程中会以此名称注册多次
- 所以当名称重复时就会以Class在下面这个List的位置来决定优先级,值越大优先级越高
- 然后就到Bean的初始化阶段,我们看AnnotationAwareAspectJAutoProxyCreator的postProcessAfterInitialization(...)方法:根据Advice和Advisor确定是否需要创建代理对象
java
复制代码
@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;
}
-
- 由于内部的调用链非常复杂又涉及到了Spring的知识点,我们这里直接单刀直入,进入GlobalMethodSecuritySelector中注册的PrePostAnnotationSecurityMetadataSource
- 再看PrePostAnnotationSecurityMetadataSource 的getAttributes(...)方法
- 很明显只有带有特定的注解返回集合才不为空,才会为Bean创建代理对象
java
复制代码
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;
}
- 而创建的代理对象以及注册到容器中的正是:DynamicAdvisedInterceptor ,就像下面这样
- 每当执行代理对象的时候就会执行其intercept(...)方法,而在intercept(...)方法中有一行代码
- 这里就会根据入参获得执行目标方法前需要执行的拦截器,这里就会有我们注册的MethodSecurityInterceptor
- 也不仅仅只这一个,像CacheInterceptor,TransactionInterceptor都是在这里工作的
3. MethodSecurityInterceptor工作流程
java
复制代码
public Object invoke(MethodInvocation mi) throws Throwable {
// 调用前的权限判断
InterceptorStatusToken token = super.beforeInvocation(mi);
Object result;
try {
// 执行真正的处理方法
result = mi.proceed();
}
finally {
// 是否需要将认证对象恢复到 判断权限之前
super.finallyInvocation(token);
}
return super.afterInvocation(token, result);
}
- invoke(...)方法的核心在于beforeInvocation(...)和afterInvocation(...)方法,这两个方法分别是在目标方法执行前和执行后校验权限的,由于beforeInvocation(...)前面讲了,所以只讲afterInvocation(...)
- afterInvocation(...):
java
复制代码
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) {
//如果token为空,就说接口是一个公共接口,不需要权限,就直接返回
if (token == null) {
return returnedObject;
}
//是否需要将认证对象恢复到 判断权限之前
finallyInvocation(token);
if (this.afterInvocationManager != null) {
try {
//处理之后目标方法后的操作
returnedObject = this.afterInvocationManager.decide(token.getSecurityContext().getAuthentication(),
token.getSecureObject(), token.getAttributes(), returnedObject);
}
catch (AccessDeniedException ex) {
//发布授权失败异常
publishEvent(new AuthorizationFailureEvent(token.getSecureObject(), token.getAttributes(),
token.getSecurityContext().getAuthentication(), ex));
throw ex;
}
}
return returnedObject;
}
4. 总结
- MethodSecurityInterceptor 其本身原理就是:
- 第一步利用SecurityMetadataSource拿到访问接口的权限属性,
- 第二步借助AuthenticationManager获取认证对象
- 第三步使用AccessDecisionManager以及里面的AccessDecisionVoter的进行投票确定,确定是否通过
- 这里其实和FilterSecurtiyFilter很像的
- 但是在上面的步骤之前要有一定的前置知识点要解锁
- Spring中是如何借助BeanPostProcessor机制完成对于Bean实例化初始化前后的回调
- 动态代理: