Spring-security源码-注解权限原理之MethodSecurityInterceptor(二十一)
MethodSecurityInterceptor是Spring Security对于Spring Aop的切入逻辑
@Override public Object invoke(MethodInvocation mi) throws Throwable { //<1>调用方法前的权限校验 InterceptorStatusToken token = super.beforeInvocation(mi); Object result; try { result = mi.proceed(); } finally { //允许重置ServletContext super.finallyInvocation(token); } //<5>调用方法后的权限校验 return super.afterInvocation(token, result); }
<1>
org.springframework.security.access.intercept.AbstractSecurityInterceptor#beforeInvocation
protected InterceptorStatusToken beforeInvocation(Object object) { Assert.notNull(object, "Object was null"); if (!getSecureObjectClass().isAssignableFrom(object.getClass())) { throw new IllegalArgumentException("Security invocation attempted for object " + object.getClass().getName() + " but AbstractSecurityInterceptor only configured to support secure objects of type: " + getSecureObjectClass()); } //<2>基于object获取对应的ConfigAttribute 也就是我们的权限配置 初始化处参考 Collection<ConfigAttribute> attributes = this.obtainSecurityMetadataSource().getAttributes(object); //如果配有配置表示不需要权限校验 if (CollectionUtils.isEmpty(attributes)) { Assert.isTrue(!this.rejectPublicInvocations, () -> "Secure object invocation " + object + " was denied as public invocations are not allowed via this interceptor. " + "This indicates a configuration error because the " + "rejectPublicInvocations property is set to 'true'"); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authorized public object %s", object)); } publishEvent(new PublicInvocationEvent(object)); return null; // no further work post-invocation } //如果未登录 抛出异常 if (SecurityContextHolder.getContext().getAuthentication() == null) { credentialsNotFound(this.messages.getMessage("AbstractSecurityInterceptor.authenticationNotFound", "An Authentication object was not found in the SecurityContext"), object, attributes); } //如果登录过期触发自动authenticationManager.authenticate 重新认证 Authentication authenticated = authenticateIfRequired(); if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Authorizing %s with attributes %s", object, attributes)); } // <3>现在有方法 和我们的权限配置则校验授权 attemptAuthorization(object, attributes, authenticated); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Authorized %s with attributes %s", object, attributes)); } if (this.publishAuthorizationSuccess) { publishEvent(new AuthorizedEvent(object, attributes, authenticated)); } // Attempt to run as a different user Authentication runAs = this.runAsManager.buildRunAs(authenticated, object, attributes); if (runAs != null) { SecurityContext origCtx = SecurityContextHolder.getContext(); SecurityContextHolder.setContext(SecurityContextHolder.createEmptyContext()); SecurityContextHolder.getContext().setAuthentication(runAs); if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Switched to RunAs authentication %s", runAs)); } // need to revert to token.Authenticated post-invocation return new InterceptorStatusToken(origCtx, true, attributes, object); } this.logger.trace("Did not switch RunAs authentication since RunAsManager returned null"); // no further work post-invocation return new InterceptorStatusToken(SecurityContextHolder.getContext(), false, attributes, object); }
<2>
org.springframework.security.access.method.DelegatingMethodSecurityMetadataSource#getAttributes
@Override public Collection<ConfigAttribute> getAttributes(Method method, Class<?> targetClass) { //方法和class作为cacheKey DelegatingMethodSecurityMetadataSource.DefaultCacheKey cacheKey = new DelegatingMethodSecurityMetadataSource.DefaultCacheKey(method, targetClass); //同步锁 synchronized (this.attributeCache) { //先判断cache是否有 Collection<ConfigAttribute> cached = this.attributeCache.get(cacheKey); // 如果有直接返回 if (cached != null) { return cached; } //遍历MethodSecurityMetadataSource 解析注解获得ConfigAttribute Collection<ConfigAttribute> attributes = null; for (MethodSecurityMetadataSource s : this.methodSecurityMetadataSources) { attributes = s.getAttributes(method, targetClass); if (attributes != null && !attributes.isEmpty()) { break; } } // Put it in the cache. if (attributes == null || attributes.isEmpty()) { this.attributeCache.put(cacheKey, NULL_CONFIG_ATTRIBUTE); return NULL_CONFIG_ATTRIBUTE; } this.attributeCache.put(cacheKey, attributes); return attributes; } }
<3>
org.springframework.security.access.intercept.AbstractSecurityInterceptor#attemptAuthorization
private void attemptAuthorization(Object object, Collection<ConfigAttribute> attributes, Authentication authenticated) { try { //<4>交给accessDecisionManager处理 初始化处参考 this.accessDecisionManager.decide(authenticated, object, attributes); } catch (AccessDeniedException ex) {//针对授权不过的抛出异常 if (this.logger.isTraceEnabled()) { this.logger.trace(LogMessage.format("Failed to authorize %s with attributes %s using %s", object, attributes, this.accessDecisionManager)); } else if (this.logger.isDebugEnabled()) { this.logger.debug(LogMessage.format("Failed to authorize %s with attributes %s", object, attributes)); } publishEvent(new AuthorizationFailureEvent(object, attributes, authenticated, ex)); throw ex; } }
<4>
@Override @SuppressWarnings({ "rawtypes", "unchecked" }) public void decide(Authentication authentication, Object object, Collection<ConfigAttribute> configAttributes) throws AccessDeniedException { int deny = 0; //遍历voter 调用vote方法进行处理 for (AccessDecisionVoter voter : getDecisionVoters()) { int result = voter.vote(authentication, object, configAttributes); switch (result) { //授权通过直接返回 case AccessDecisionVoter.ACCESS_GRANTED: return; //针对授权不过 返回ACCESS_DENIED case AccessDecisionVoter.ACCESS_DENIED: deny++; break; default: break; } } if (deny > 0) { throw new AccessDeniedException( this.messages.getMessage("AbstractAccessDecisionManager.accessDenied", "Access is denied")); } // To get this far, every AccessDecisionVoter abstained checkAllowIfAllAbstainDecisions(); }
<5>
protected Object afterInvocation(InterceptorStatusToken token, Object returnedObject) { if (token == null) { // public object return returnedObject; } finallyInvocation(token); // continue to clean in this method for passivity if (this.afterInvocationManager != null) { // Attempt after invocation handling 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; }