1、开启注解功能
Spring Security默认是禁用注解的,要想开启注解功能需要在@Configuration类上加入@EnableMethodSecurity注解来判断用户对某个控制层的方法是否具有访问权限。
@Configuration
@EnableGlobalMethodSecurity(prePostEnabled =true)
public class WebSecurityConfigextends WebSecurityConfigurerAdapter {……}
@EnableGlobalMethodSecurity属性说明:
- prePostEnabled: 确定是否应启用Spring Security的预发布注释。
- securedEnabled:确定是否应启用Spring Security的@Secured 注解。
- jsr250Enabled:确定是否应启用JSR-250注释。
2、主要的bean
@Import({ GlobalMethodSecuritySelector.class })
@EnableGlobalAuthentication
@Configuration
public @interface EnableGlobalMethodSecurity {
...
}
可以看到@EnableGlobalMethodSecurity引入了一个@EnableGlobalAuthentication注解,这个注解我之前分析过了主要是引入了一个Spring容器提供的AuthenticationManager,详情请参考【Spring-Security源码分析】Spring Security启动过程。这里主要关注GlobalMethodSecuritySelector。
final class GlobalMethodSecuritySelector implements ImportSelector {
public final 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) {
//通过MethodSecurityMetadataSourceAdvisorRegistrar注册的bean需要的构造参数定义在这里
classNames.add(GlobalMethodSecurityConfiguration.class.getName());
}
//jsr250的支持
if (jsr250Enabled) {
classNames.add(Jsr250MetadataSourceConfiguration.class.getName());
}
return classNames.toArray(new String[0]);
}
}
2.1、MethodSecurityMetadataSourceAdvisor的创建
上面代码返回一个MethodSecurityMetadataSourceAdvisorRegistrar,这个类会向Spring容器注册一个MethodSecurityMetadataSourceAdvisor。
class MethodSecurityMetadataSourceAdvisorRegistrar implements
ImportBeanDefinitionRegistrar {
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());
}
}
MethodSecurityMetadataSourceAdvisor构造函数需要的三个参数,methodSecurityInterceptor(一个拦截器的bean name)、methodSecurityMetadataSource(Spring容器定义的bean)、methodSecurityMetadataSource(前者的bean name)定义在GlobalMethodSecurityConfiguration,我们稍后再看。
这里我们主要关注MethodSecurityMetadataSourceAdvisor会拦截哪些方法和持有的Advice。
MethodSecurityMetadataSourceAdvisor的getPointcut()返回的Pointcut是一个MethodSecurityMetadataSourcePointcut,具体拦截行为如下:
class MethodSecurityMetadataSourcePointcut extends StaticMethodMatcherPointcut
implements Serializable {
@SuppressWarnings("unchecked")
public boolean matches(Method m, Class targetClass) {
//attributeSource是MethodSecurityMetadataSourceAdvisor构造方法传入的MethodSecurityMetadataSource实例
Collection attributes = attributeSource.getAttributes(m, targetClass);
return attributes != null && !attributes.isEmpty();
}
}
getAdvice()方法返回的是从Spring容器中获取名为methodSecurityInterceptor的MethodInterceptor,所以我们只要弄明白了Spring容器中提供的methodSecurityInterceptor和methodSecurityMetadataSource,就清楚了基于注解权限验证的原理。
2.2、methodSecurityInterceptor和methodSecurityMetadataSource的定义
这两个重要的bean定义在GlobalMethodSecurityConfiguration中。
2.2.1、DelegatingMethodSecurityMetadataSource
@Bean
public MethodSecurityMetadataSource methodSecurityMetadataSource() {
List<MethodSecurityMetadataSource> sources = new ArrayList<>();
//为PrePostAnnotationSecurityMetadataSource服务,提到它时再说
ExpressionBasedAnnotationAttributeFactory attributeFactory = new ExpressionBasedAnnotationAttributeFactory(
getExpressionHandler());
MethodSecurityMetadataSource customMethodSecurityMetadataSource = customMethodSecurityMetadataSource();
if (customMethodSecurityMetadataSource != null) {
sources.add(customMethodSecurityMetadataSource);
}
//可以自定义customMethodSecurityMetadataSource
boolean hasCustom = customMethodSecurityMetadataSource != null;
boolean isPrePostEnabled = prePostEnabled();
boolean isSecuredEnabled = securedEnabled();
boolean isJsr250Enabled = jsr250Enabled();
//支持的注解都没开启就没必要开启了
if (!isPrePostEnabled && !isSecuredEnabled && !isJsr250Enabled && !hasCustom) {
throw new IllegalStateException("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);
}
这里定义了一个DelegatingMethodSecurityMetadataSource它是PrePostAnnotationSecurityMetadataSource、SecuredAnnotationSecurityMetadataSource、Jsr250MethodSecurityMetadataSource的组合。这个bean是为MethodSecurityInterceptor提供ConfigAttribute的。
2.2.1、MethodSecurityInterceptor
@Bean
public MethodInterceptor methodSecurityInterceptor() throws Exception {
this.methodSecurityInterceptor = isAspectJ()
? new AspectJMethodSecurityInterceptor()
: new MethodSecurityInterceptor();
methodSecurityInterceptor.setAccessDecisionManager(accessDecisionManager());
methodSecurityInterceptor.setAfterInvocationManager(afterInvocationManager());
methodSecurityInterceptor
.setSecurityMetadataSource(methodSecurityMetadataSource());
RunAsManager runAsManager = runAsManager();
if (runAsManager != null) {
methodSecurityInterceptor.setRunAsManager(runAsManager);
}
return this.methodSecurityInterceptor;
}
MethodSecurityInterceptor的invoke()方法的拦截功能实现委托给了父类的beforeInvocation()、finallyInvocation()、afterInvocation()方法,在《【Spring-Security源码分析】WebSecurity》我们分析过AbstractSecurityInterceptor的实现可知beforeInvocation()中是通过accessDecisionManager的decide()方法检查权限,执行完目标方法后在afterInvocation()方法中通过afterInvocationManager的decide()的方法再次检查权限。
accessDecisionManager是一个持有PreInvocationAuthorizationAdviceVoter、Jsr250Voter、RoleVoter、AuthenticatedVoter的AffirmativeBased。
protected AccessDecisionManager accessDecisionManager() {
List<AccessDecisionVoter<? extends Object>> decisionVoters = new ArrayList<AccessDecisionVoter<? extends Object>>();
ExpressionBasedPreInvocationAdvice expressionAdvice = new ExpressionBasedPreInvocationAdvice();
expressionAdvice.setExpressionHandler(getExpressionHandler());
if (prePostEnabled()) {
decisionVoters
.add(new PreInvocationAuthorizationAdviceVoter(expressionAdvice));
}
if (jsr250Enabled()) {
decisionVoters.add(new Jsr250Voter());
}
decisionVoters.add(new RoleVoter());
decisionVoters.add(new AuthenticatedVoter());
return new AffirmativeBased(decisionVoters);
}
afterInvocationManager是内部使用PostInvocationAuthorizationAdvice完成集合的过滤(如果目标方法返回的是集合)和再次权限的验证。
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;
}
我们先从accessDecisionManager的实现开始分析,它内部有4种AccessDecisionVoter,不同的AccessDecisionVoter负责处理不同ConfigAttribute的类型通过supports()方法。
先看PreInvocationAuthorizationAdviceVoter的supports()方法。
public boolean supports(ConfigAttribute attribute) {
return attribute instanceof PreInvocationAttribute;
}
它支持PreInvocationAttribute这种ConfigAttribute的投票,那PreInvocationAttribute是怎么来了呢?
在AbstractSecurityInterceptor#beforeInvocation()方法被调用时会使用MethodSecurityInterceptor持有的DelegatingMethodSecurityMetadataSource取得与目标对象相关联的ConfigAttribute集合传给accessDecisionManager完成授权。还记得上面讲过DelegatingMethodSecurityMetadataSource是多个SecurityMetadataSource的组合吗,其中就存在PrePostAnnotationSecurityMetadataSource用来根据方法上的注解@PreFilter、@PreAuthorize和@PostFilter、PostAuthorize生成PreInvocationAttribute和PostInvocationAttribute。
PrePostAnnotationSecurityMetadataSource概述
MethodSecurityMetadataSource,用于从放置在方法上的@PreFilter和@PreAuthorize注释中提取元数据。 该类仅负责查找相关注释(如果有)。 它将实际的ConfigAttribute创建委托给其PrePostInvocationAttributeFactory,从而将自身与将强制注释行为的机制分离。
可以在类或方法上指定注释,并且特定于方法的注释优先。 如果您使用任何注释并且未指定预授权条件,则将允许该方法,就像存在@PreAuthorize(“permitAll”)一样。
由于我们在这里处理多个注释,因此我们可能必须将在多个位置定义的注释组合为单个方法 - 它们可以在方法本身上定义,也可以在接口或类级别定义。
PrePostAnnotationSecurityMetadataSource的getAttributes(Object object)定义在父类AbstractMethodSecurityMetadataSource中,
public abstract class AbstractMethodSecurityMetadataSource implements
MethodSecurityMetadataSource {
protected final Log logger = LogFactory.getLog(getClass());
public final Collection<ConfigAttribute> getAttributes(Object object) {
if (object instanceof MethodInvocation) {
MethodInvocation mi = (MethodInvocation) object;
Object target = mi.getThis();
Class<?> targetClass = null;
if (target != null) {
targetClass = target instanceof Class<?> ? (Class<?>) target
: AopProxyUtils.ultimateTargetClass(target);
}
Collection<ConfigAttribute> attrs = getAttributes(mi.getMethod(), targetClass);
if (attrs != null && !attrs.isEmpty()) {
return attrs;
}
if (target != null && !(target instanceof Class<?>)) {
attrs = getAttributes(mi.getMethod(), target.getClass());
}
return attrs;
}
throw new IllegalArgumentException("Object must be a non-null MethodInvocation");
}
public final boolean supports(Class<?> clazz) {
return (MethodInvocation.class.isAssignableFrom(clazz));
}
}
子类实现getAttributes(Method method, Class<?> targetClass)方法。
public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) {
if (method.getDeclaringClass() == Object.class) {
return Collections.emptyList();
}
logger.trace("Looking for Pre/Post annotations for method '" + method.getName()
+ "' on target class '" + targetClass + "'");
PreFilter preFilter = findAnnotation(method, targetClass, PreFilter.class);
PreAuthorize preAuthorize = findAnnotation(method, targetClass,
PreAuthorize.class);
PostFilter postFilter = findAnnotation(method, targetClass, PostFilter.class);
// TODO: Can we check for void methods and throw an exception here?
PostAuthorize postAuthorize = findAnnotation(method, targetClass,
PostAuthorize.class);
if (preFilter == null && preAuthorize == null && postFilter == null
&& postAuthorize == null) {
// There is no meta-data so return
logger.trace("No expression annotations found");
return Collections.emptyList();
}
String preFilterAttribute = preFilter == null ? null : preFilter.value();
String filterObject = preFilter == null ? null : preFilter.filterTarget();
String preAuthorizeAttribute = preAuthorize == null ? null : preAuthorize.value();
String postFilterAttribute = postFilter == null ? null : postFilter.value();
String postAuthorizeAttribute = postAuthorize == null ? null : postAuthorize
.value();
ArrayList<ConfigAttribute> attrs = new ArrayList<>(2);
PreInvocationAttribute pre = attributeFactory.createPreInvocationAttribute(
preFilterAttribute, filterObject, preAuthorizeAttribute);
if (pre != null) {
attrs.add(pre);
}
PostInvocationAttribute post = attributeFactory.createPostInvocationAttribute(
postFilterAttribute, postAuthorizeAttribute);
if (post != null) {
attrs.add(post);
}
attrs.trimToSize();
return attrs;
}
PreInvocationAttribute和PostInvocationAttribute的创建分别使用ExpressionBasedAnnotationAttributeFactory的createPreInvocationAttribute()和createPostInvocationAttribute()。
public class ExpressionBasedAnnotationAttributeFactory implements
PrePostInvocationAttributeFactory {
private final Object parserLock = new Object();
private ExpressionParser parser;
private MethodSecurityExpressionHandler handler;
public ExpressionBasedAnnotationAttributeFactory(
MethodSecurityExpressionHandler handler) {
this.handler = handler;
}
public PreInvocationAttribute createPreInvocationAttribute(String preFilterAttribute,
String filterObject, String preAuthorizeAttribute) {
try {
// TODO: Optimization of permitAll
ExpressionParser parser = getParser();
Expression preAuthorizeExpression = preAuthorizeAttribute == null ? parser
.parseExpression("permitAll") : parser
.parseExpression(preAuthorizeAttribute);
Expression preFilterExpression = preFilterAttribute == null ? null : parser
.parseExpression(preFilterAttribute);
return new PreInvocationExpressionAttribute(preFilterExpression,
filterObject, preAuthorizeExpression);
}
catch (ParseException e) {
throw new IllegalArgumentException("Failed to parse expression '"
+ e.getExpressionString() + "'", e);
}
}
public PostInvocationAttribute createPostInvocationAttribute(
String postFilterAttribute, String postAuthorizeAttribute) {
try {
ExpressionParser parser = getParser();
Expression postAuthorizeExpression = postAuthorizeAttribute == null ? null
: parser.parseExpression(postAuthorizeAttribute);
Expression postFilterExpression = postFilterAttribute == null ? null : parser
.parseExpression(postFilterAttribute);
if (postFilterExpression != null || postAuthorizeExpression != null) {
return new PostInvocationExpressionAttribute(postFilterExpression,
postAuthorizeExpression);
}
}
catch (ParseException e) {
throw new IllegalArgumentException("Failed to parse expression '"
+ e.getExpressionString() + "'", e);
}
return null;
}
private ExpressionParser getParser() {
if (this.parser != null) {
return this.parser;
}
synchronized (parserLock) {
this.parser = handler.getExpressionParser();
this.handler = null;
}
return this.parser;
}
}
关于Expression的求值过程请参考《【Spring-Security源码分析】Spring安全表达式解析》。
知道了PreInvocationAttribute的获取过程下面分析PreInvocationAuthorizationAdviceVoter的vote()方法实现。
public int vote(Authentication authentication, MethodInvocation method,
Collection<ConfigAttribute> attributes) {
//查找prefilter和preauth(或组合)属性如果两者都为null,则禁止访问
PreInvocationAttribute preAttr = findPreInvocationAttribute(attributes);
if (preAttr == null) {
// No expression based metadata, so abstain
return ACCESS_ABSTAIN;
}
boolean allowed = preAdvice.before(authentication, method, preAttr);
return allowed ? ACCESS_GRANTED : ACCESS_DENIED;
}
ExpressionBasedPreInvocationAdvice#before()方法实现。
public boolean before(Authentication authentication, MethodInvocation mi,
PreInvocationAttribute attr) {
PreInvocationExpressionAttribute preAttr = (PreInvocationExpressionAttribute) attr;
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
mi);
Expression preFilter = preAttr.getFilterExpression();
Expression preAuthorize = preAttr.getAuthorizeExpression();
if (preFilter != null) {
Object filterTarget = findFilterTarget(preAttr.getFilterTarget(), ctx, mi);
expressionHandler.filter(filterTarget, preFilter, ctx);
}
if (preAuthorize == null) {
return true;
}
return ExpressionUtils.evaluateAsBoolean(preAuthorize, ctx);
}
private Object findFilterTarget(String filterTargetName, EvaluationContext ctx,
MethodInvocation mi) {
Object filterTarget = null;
if (filterTargetName.length() > 0) {
filterTarget = ctx.lookupVariable(filterTargetName);
if (filterTarget == null) {
throw new IllegalArgumentException(
"Filter target was null, or no argument with name "
+ filterTargetName + " found in method");
}
}
else if (mi.getArguments().length == 1) {
Object arg = mi.getArguments()[0];
if (arg.getClass().isArray() || arg instanceof Collection<?>) {
filterTarget = arg;
}
if (filterTarget == null) {
throw new IllegalArgumentException(
"A PreFilter expression was set but the method argument type"
+ arg.getClass() + " is not filterable");
}
}
if (filterTarget.getClass().isArray()) {
throw new IllegalArgumentException(
"Pre-filtering on array types is not supported. "
+ "Using a Collection will solve this problem");
}
return filterTarget;
}
在上面before()方法中除了权限检查外还可以完成对@PreFilter filterTarger指定的EvaluationContext中的属性集合属性进行过滤,通过下面DefaultMethodSecurityExpressionHandler的filter()方法可知,此filterTarget属性对象的表达式必须为"filterObject"。
//通过评估提供的表达式过滤filterTarget对象(必须是集合或数组)。
//如果使用Collection,则将修改原始实例以包含权限表达式求值为true的元素。 对于数组,将返回新的数组实例。
public Object filter(Object filterTarget, Expression filterExpression,
EvaluationContext ctx) {
MethodSecurityExpressionOperations rootObject = (MethodSecurityExpressionOperations) ctx
.getRootObject().getValue();
final boolean debug = logger.isDebugEnabled();
List retainList;
if (debug) {
logger.debug("Filtering with expression: "
+ filterExpression.getExpressionString());
}
if (filterTarget instanceof Collection) {
Collection collection = (Collection) filterTarget;
retainList = new ArrayList(collection.size());
if (debug) {
logger.debug("Filtering collection with " + collection.size()
+ " elements");
}
if (permissionCacheOptimizer != null) {
permissionCacheOptimizer.cachePermissionsFor(
rootObject.getAuthentication(), collection);
}
for (Object filterObject : (Collection) filterTarget) {
//从这里可以看出是针对根对象的filterObject属性过滤的
rootObject.setFilterObject(filterObject);
if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
retainList.add(filterObject);
}
}
if (debug) {
logger.debug("Retaining elements: " + retainList);
}
collection.clear();
collection.addAll(retainList);
return filterTarget;
}
if (filterTarget.getClass().isArray()) {
Object[] array = (Object[]) filterTarget;
retainList = new ArrayList(array.length);
if (debug) {
logger.debug("Filtering array with " + array.length + " elements");
}
if (permissionCacheOptimizer != null) {
permissionCacheOptimizer.cachePermissionsFor(
rootObject.getAuthentication(), Arrays.asList(array));
}
for (Object o : array) {
rootObject.setFilterObject(o);
if (ExpressionUtils.evaluateAsBoolean(filterExpression, ctx)) {
retainList.add(o);
}
}
if (debug) {
logger.debug("Retaining elements: " + retainList);
}
Object[] filtered = (Object[]) Array.newInstance(filterTarget.getClass()
.getComponentType(), retainList.size());
for (int i = 0; i < retainList.size(); i++) {
filtered[i] = retainList.get(i);
}
return filtered;
}
throw new IllegalArgumentException(
"Filter target must be a collection or array type, but was "
+ filterTarget);
}
PostInvocationAuthorizationAdvice用于调用目标方法后的验证。
public class ExpressionBasedPostInvocationAdvice implements
PostInvocationAuthorizationAdvice {
protected final Log logger = LogFactory.getLog(getClass());
private final MethodSecurityExpressionHandler expressionHandler;
public ExpressionBasedPostInvocationAdvice(
MethodSecurityExpressionHandler expressionHandler) {
this.expressionHandler = expressionHandler;
}
public Object after(Authentication authentication, MethodInvocation mi,
PostInvocationAttribute postAttr, Object returnedObject)
throws AccessDeniedException {
PostInvocationExpressionAttribute pia = (PostInvocationExpressionAttribute) postAttr;
EvaluationContext ctx = expressionHandler.createEvaluationContext(authentication,
mi);
Expression postFilter = pia.getFilterExpression();
Expression postAuthorize = pia.getAuthorizeExpression();
if (postFilter != null) {
if (logger.isDebugEnabled()) {
logger.debug("Applying PostFilter expression " + postFilter);
}
//过滤的是返回的集合对象
if (returnedObject != null) {
returnedObject = expressionHandler
.filter(returnedObject, postFilter, ctx);
}
else {
if (logger.isDebugEnabled()) {
logger.debug("Return object is null, filtering will be skipped");
}
}
}
//验证规则可针对returnObject
expressionHandler.setReturnObject(returnedObject, ctx);
if (postAuthorize != null
&& !ExpressionUtils.evaluateAsBoolean(postAuthorize, ctx)) {
if (logger.isDebugEnabled()) {
logger.debug("PostAuthorize expression rejected access");
}
throw new AccessDeniedException("Access is denied");
}
return returnedObject;
}
}