方法级别访问控制源码分析
开启
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class MethodSecurityConfig extends GlobalMethodSecurityConfiguration {
}
@EnableGlobalMethodSecurity 向 IOC 容器导入的 spring bean:
AutoProxyRegistrar(为某些spring bean 创建代理)MethodSecurityMetadataSourceAdvisorRegistrar。这个 bean 的作用是导入:MethodSecurityMetadataSourceAdvisorbean。内部有一个MethodInterceptor
AuthenticationConfiguration。这个又会导入下面几个 spring bean:AuthenticationManagerBuilder。实现类:DefaultPasswordEncoderAuthenticationManagerBuilder
MethodInterceptor
MethodSecurityMetadataSourceAdvisor
public class MethodSecurityMetadataSourceAdvisor extends AbstractPointcutAdvisor implements BeanFactoryAware {
private transient MethodSecurityMetadataSource attributeSource;
// 方法拦截器。实际类型是:MethodSecurityInterceptor
// 标注权限注解的方法,调用的时候,会被这个组件拦截
private transient MethodInterceptor interceptor;
// 配置拦截什么方法。这里会拦截 @PreAuthorize, @PostAuthorize 等 security 注解标注的方法
private final Pointcut pointcut = new MethodSecurityMetadataSourcePointcut();
private BeanFactory beanFactory;
private final String adviceBeanName;
private final String metadataSourceBeanName;
private transient volatile Object adviceMonitor = new Object();
public MethodSecurityMetadataSourceAdvisor(String adviceBeanName, MethodSecurityMetadataSource attributeSource,
String attributeSourceBeanName) {
Assert.notNull(adviceBeanName, "The adviceBeanName cannot be null");
Assert.notNull(attributeSource, "The attributeSource cannot be null");
Assert.notNull(attributeSourceBeanName, "The attributeSourceBeanName cannot be null");
this.adviceBeanName = adviceBeanName;
this.attributeSource = attributeSource;
this.metadataSourceBeanName = attributeSourceBeanName;
}
//
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));
}
}
}
MethodSecurityInterceptor
public class MethodSecurityInterceptor extends AbstractSecurityInterceptor implements MethodInterceptor {
private MethodSecurityMetadataSource securityMetadataSource;
@Override
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);
}
}
看看其父类 AbstractSecurityInterceptor 有哪些重要的对象
public abstract class AbstractSecurityInterceptor
implements InitializingBean, ApplicationEventPublisherAware, MessageSourceAware {
// ---
// 验证能不能访问该方法
private AccessDecisionManager accessDecisionManager;
// 方法被调用之后,怎么做。比如验证 @PostAuthorize 注解
private AfterInvocationManager afterInvocationManager;
// ---
}
-
会组合哪些
AccessDecisionManager呢?需要根据@EnableGlobalMethodSecurity这个注解配置注解配置 投票器 @EnableGlobalMethodSecurity(prePostEnabled = true)PreInvocationAuthorizationAdviceVoter@EnableGlobalMethodSecurity(jsr250Enabled = true)Jsr250Voter另加两个
voterRoleVoterAuthenticatedVoter
AccessDecisionVoter
spring security 中,一个用户有没有权限访问某些权限注解标注的方法,由 AccessDecisionVoter 决定。
主要有四个:
PreInvocationAuthorizationAdviceVoter.prePostEnabled = true的时候,会添加。基于 SpELJsr250Voter。jsr250Enabled = true的时候,会添加。 如何使用jsr250可以看下面RoleVoter。AuthenticatedVoter。
jsr250
提供的注解,主要有三个:
@RolesAllowed@PermitAll@DenyAll
可以看到,控制的粒度会很大,要么根据角色,要么全部都可以访问、要么全都不可以访问
@Service
public class MyService {
@RolesAllowed("ROLE_USER")
public void userMethod() {
// 只有具有 "ROLE_USER" 角色的用户可以访问这个方法
}
@RolesAllowed({"ROLE_ADMIN", "ROLE_USER"})
public void adminOrUserMethod() {
// 只有具有 "ROLE_ADMIN" 或 "ROLE_USER" 角色的用户可以访问这个方法
}
@PermitAll
public void permitAllMethod() {
// 任何用户都可以访问这个方法
}
@DenyAll
public void denyAllMethod() {
// 任何用户都不能访问这个方法
}
}
SpEL
看下 spring security 如何基于 spel 验证 @PreAuthorize 注解的。
创建 root 对象
先说下 spel 中 root object。spel 的表达式中需要访问属性、方法的时候,都会从 root object 对象中获取。
下面的 root object 是 MethodSecurityExpressionRoot 。MethodSecurityExpressionRoot 是 SecurityExpressionOperations的实现类
class MethodSecurityExpressionRoot extends SecurityExpressionRoot implements MethodSecurityExpressionOperations{}
SecurityExpressionOperations
public interface SecurityExpressionOperations {
boolean hasAuthority(String authority);
boolean hasAnyAuthority(String... authorities);
boolean hasRole(String role);
boolean hasAnyRole(String... roles);
boolean hasPermission(Object target, Object permission);
boolean hasPermission(Object targetId, String targetType, Object permission);
// ... 省略
}
看到这些方法之后,就会明白为啥 @PreAuthorize 会这样使用:
@PreAuthorize("hasRole('user')")@PostAuthorize("hasPermission('foo', 'bar')")@PreAuthorize("hasAnyRole('ROLE_ADMIN', 'ROLE_USER')")@PreAuthorize("hasAuthority('READ_PRIVILEGE')")@PreAuthorize("hasAnyAuthority('READ_PRIVILEGE', 'WRITE_PRIVILEGE')")
@Override
protected MethodSecurityExpressionOperations createSecurityExpressionRoot(Authentication authentication,
MethodInvocation invocation) {
MethodSecurityExpressionRoot root = new MethodSecurityExpressionRoot(authentication);
root.setThis(invocation.getThis());
root.setPermissionEvaluator(getPermissionEvaluator());
root.setTrustResolver(getTrustResolver());
root.setRoleHierarchy(getRoleHierarchy());
root.setDefaultRolePrefix(getDefaultRolePrefix());
return root;
}
创建 EvaluationContext
SecurityExpressionOperations root = createSecurityExpressionRoot(authentication, invocation);
// 使用的是 StandardEvaluationContext
StandardEvaluationContext ctx = createEvaluationContextInternal(authentication, invocation);
// 让 spel 中能用 @ 符号引用 spring bean
ctx.setBeanResolver(this.beanResolver);
// 配置 root object
ctx.setRootObject(root);
接下来验证 @PreAutorize 里面的表达式
// 假如是这样的
@PreAuthorize("hasRole('user')")
//
ExpressionParser parser = new SpelExpressionParser();
Expression expression = parser.parse("hasRole('user')");
// ctx 就是上一步的 StandardEvaluationContext
boolean access = expression.getValue(ctx, Boolean.class);
// access为true,能访问;为 false,不能访问