上一篇文章介紹了shiro在spring-boot中經過filter實現authentication流程(經過設置filterMaps也能夠達到authorization的目的);這篇文章主要介紹spring經過AOP的方式實現shiro的authorization的流程。ios
shiro-spring-boot-web-starter
除了自身在META-INF中定義了ShiroWebAutoConfiguration
和ShiroWebFilterConfiguration
外,還在pom
文件中引用了shiro-spring-boot-stater
。然後者在本身的META-INF
文件中又定義了三個配置類:web
ShiroAutoConfiguration
:主要將shiro中重要的組件聲明成bean。大部分配置被ShiroWebAutoConfiguration
中的bean取代。ShiroBeanAutoConfiguration
:主要設置了EventBus
(便於監聽各類事件)和LifecycleBeanPostProcessor
(生命週期管理,對象的初始化和銷燬)。ShiroAnnotationProcessorAutoConfiguration
:顧名思義,shiro註解處理相關的bean都在這個類中配置。@SuppressWarnings("SpringFacetCodeInspection") @Configuration @ConditionalOnProperty(name = "shiro.annotations.enabled", matchIfMissing = true) public class ShiroAnnotationProcessorAutoConfiguration extends AbstractShiroAnnotationProcessorConfiguration { //負責建立代理類的對象 @Bean @DependsOn("lifecycleBeanPostProcessor") @ConditionalOnMissingBean @Override public DefaultAdvisorAutoProxyCreator defaultAdvisorAutoProxyCreator() { return super.defaultAdvisorAutoProxyCreator(); } //聲明瞭Adviosr,Advisor聲明瞭Pointcut和Advice,即規定了在哪些地方作哪些事 @Bean @ConditionalOnMissingBean @Override public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) { return super.authorizationAttributeSourceAdvisor(securityManager); } }
因此shiro經過聲明瞭Advisor,以AOP的方式在執行某些方法前先進行權限校驗。spring
DefaultAdvisorAutoProxyCreator
是spring框架提供的用來建立代理的類。能夠經過這個類理清spring建立代理的流程。先了解DefaultAdvisorAutoProxyCreator
的類繼承關係。圖中刪除了部分繼承關係,只保留了最主要的內容:
apache
從接口的繼承關係中能夠看到,該類的處理可能處於類的實例化先後(Instantiation)和初始化先後(Initialization)。
下面的分析將以Bean的建立流程爲順序。數組
postProcessBeforeInstantiation()
中。//實例化前置處理(該方法會在bean實例化前調用,且若是該方法返回不會空,則不會在建立bean的實例) @Override public Object postProcessBeforeInstantiation(Class<?> beanClass, String beanName) throws BeansException { Object cacheKey = getCacheKey(beanClass, beanName); // if (beanName == null || !this.targetSourcedBeans.contains(beanName)) { //是否已經被代理過 if (this.advisedBeans.containsKey(cacheKey)) { return null; } //AOP相關的系統類 和 須要跳過的類(交由子類根據具體需求拓展) 不須要代理 if (isInfrastructureClass(beanClass) || shouldSkip(beanClass, beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return null; } } //若是定義了符合該Bean的TargetSource,那麼使用TargetSource爲該Bean建立代理 //TargetSource可讓用戶自定義代理的過程 if (beanName != null) { TargetSource targetSource = getCustomTargetSource(beanClass, beanName); if (targetSource != null) { this.targetSourcedBeans.add(beanName); Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(beanClass, beanName, targetSource); Object proxy = createProxy(beanClass, beanName, specificInterceptors, targetSource); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } } return null; }
postProcessBeanBeforeInstantication
中已經建立了Bean的代理對象,則會跳過createBean
的過程。postProcessAfterInstantication()
:boolean
型的值,決定是否繼續執行是剩下的InstantationAwareBeanPostProcessor
。postProcessBeforeInitialization()
:這裏不對bean作任務處理直接返回。@Override public Object postProcessBeforeInitialization(Object bean, String beanName) { return bean; }
postProcessAfterInitialization()
。這一步是spring建立代理的過程。@Override public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException { if (bean != null) { //獲取緩存的key Object cacheKey = getCacheKey(bean.getClass(), beanName); //獲取是否在以前已經對其代理過 if (!this.earlyProxyReferences.contains(cacheKey)) { //若是須要代理,則對其進行包裝 return wrapIfNecessary(bean, beanName, cacheKey); } } return bean; }
其中的wrapIfNecessary
就是爲bean建立代理的過程。先判斷該bean是否須要建立代理,若是須要則建立代理封裝該bean。緩存
protected Object wrapIfNecessary(Object bean, String beanName, Object cacheKey) { //判斷是否已經由TargetSource產生過代理 if (beanName != null && this.targetSourcedBeans.contains(beanName)) { return bean; } //判斷是否已經解析過該bean,且結果是不須要代理 if (Boolean.FALSE.equals(this.advisedBeans.get(cacheKey))) { return bean; } //判斷是不是AOP相關類 或是 不須要代理的類 if (isInfrastructureClass(bean.getClass()) || shouldSkip(bean.getClass(), beanName)) { this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; } //獲取該Bean相關的Advice Object[] specificInterceptors = getAdvicesAndAdvisorsForBean(bean.getClass(), beanName, null); //若是不等於空,則說明須要代理 if (specificInterceptors != DO_NOT_PROXY) { this.advisedBeans.put(cacheKey, Boolean.TRUE); //建立代理 Object proxy = createProxy( bean.getClass(), beanName, specificInterceptors, new SingletonTargetSource(bean)); this.proxyTypes.put(cacheKey, proxy.getClass()); return proxy; } this.advisedBeans.put(cacheKey, Boolean.FALSE); return bean; }
決定這個bean是否要代理的一個重要過程是getAdvicesAndAdvisorsForBean()
。這個方法會返回須要應用在該bean上的advice或是advisor。若是返回爲空,則說明不須要代理。這個方法的具體實現是在AbstractAdvisorAutoProxyCreator
。app
//獲取能夠應用在該bean上的advise或advisor @Override protected Object[] getAdvicesAndAdvisorsForBean(Class<?> beanClass, String beanName, TargetSource targetSource) { //具體查找方法交給findEligibleAdvisors實現 List<Advisor> advisors = findEligibleAdvisors(beanClass, beanName); //若是沒找到,則返回特定對象 表示不須要代理 if (advisors.isEmpty()) { return DO_NOT_PROXY; } //不然轉成數組返回 return advisors.toArray(); } //查詢覈實的advisor方法 protected List<Advisor> findEligibleAdvisors(Class<?> beanClass, String beanName) { //找出全部的advisor作候選 List<Advisor> candidateAdvisors = findCandidateAdvisors(); //再在候選的advisor篩選出適用的 List<Advisor> eligibleAdvisors = findAdvisorsThatCanApply(candidateAdvisors, beanClass, beanName); //拓展Advisor extendAdvisors(eligibleAdvisors); //排序 if (!eligibleAdvisors.isEmpty()) { eligibleAdvisors = sortAdvisors(eligibleAdvisors); } return eligibleAdvisors; } //查找候選advisor的方法委託給BeanFactoryAdvisorRetrievalHelper protected List<Advisor> findCandidateAdvisors() { return this.advisorRetrievalHelper.findAdvisorBeans(); } //獲取適用的Advisor,主要委託給AopUtil protected List<Advisor> findAdvisorsThatCanApply( List<Advisor> candidateAdvisors, Class<?> beanClass, String beanName) { ProxyCreationContext.setCurrentProxiedBeanName(beanName); try { return AopUtils.findAdvisorsThatCanApply(candidateAdvisors, beanClass); } finally { ProxyCreationContext.setCurrentProxiedBeanName(null); } } /** * Return whether the Advisor bean with the given name is eligible * for proxying in the first place. * @param beanName the name of the Advisor bean * @return whether the bean is eligible */ protected boolean isEligibleAdvisorBean(String beanName) { return true; } //對Advisor排序 protected List<Advisor> sortAdvisors(List<Advisor> advisors) { AnnotationAwareOrderComparator.sort(advisors); return advisors; }
BeanFactoryAdvisorRetrievalHelper.findAdvisorBeans()
大概過程就是先經過在beanFactory中查詢類型爲Advisor.class
或其子類的的bean的name。而後根據beanName,再從beanFactory中根據beanName獲取對應的Advisor
的bean。框架
public List<Advisor> findAdvisorBeans() { // 若是已經緩存過,則直接使用緩存的結果 String[] advisorNames = this.cachedAdvisorBeanNames; //沒緩存 則在BeanFactory中搜索一次 if (advisorNames == null) { // Do not initialize FactoryBeans here: We need to leave all regular beans // uninitialized to let the auto-proxy creator apply to them! //根據Advisor類型查詢 //這裏只是獲取bean的name,並未進行實例化 advisorNames = BeanFactoryUtils.beanNamesForTypeIncludingAncestors( this.beanFactory, Advisor.class, true, false); this.cachedAdvisorBeanNames = advisorNames; } if (advisorNames.length == 0) { return new ArrayList<Advisor>(); } List<Advisor> advisors = new ArrayList<Advisor>(); //根據beanName獲取對應的Advisor的bean for (String name : advisorNames) { if (isEligibleBean(name)) { if (this.beanFactory.isCurrentlyInCreation(name)) { if (logger.isDebugEnabled()) { logger.debug("Skipping currently created advisor '" + name + "'"); } } else { try { //實例化advisor的bean advisors.add(this.beanFactory.getBean(name, Advisor.class)); } catch (BeanCreationException ex) { Throwable rootCause = ex.getMostSpecificCause(); if (rootCause instanceof BeanCurrentlyInCreationException) { BeanCreationException bce = (BeanCreationException) rootCause; if (this.beanFactory.isCurrentlyInCreation(bce.getBeanName())) { if (logger.isDebugEnabled()) { logger.debug("Skipping advisor '" + name + "' with dependency on currently created bean: " + ex.getMessage()); } // Ignore: indicates a reference back to the bean we're trying to advise. // We want to find advisors other than the currently created bean itself. continue; } } throw ex; } } } } return advisors; }
再來看決定Advisors是否適用的過程:AopUtils.findAdvisorsThatCanApply()
。ide
public static List<Advisor> findAdvisorsThatCanApply(List<Advisor> candidateAdvisors, Class<?> clazz) { if (candidateAdvisors.isEmpty()) { return candidateAdvisors; } List<Advisor> eligibleAdvisors = new LinkedList<Advisor>(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor && canApply(candidate, clazz)) { eligibleAdvisors.add(candidate); } } boolean hasIntroductions = !eligibleAdvisors.isEmpty(); for (Advisor candidate : candidateAdvisors) { if (candidate instanceof IntroductionAdvisor) { // already processed continue; } if (canApply(candidate, clazz, hasIntroductions)) { eligibleAdvisors.add(candidate); } } return eligibleAdvisors; }
主要是將Advisor根據不一樣的類型分紅兩類:IntroducationAdvisor
和PointcutAdvisor
。兩種Advisor由於類型不一樣,因此判斷方式也不同。IntroductionAdvisor
由於是類級別的攔截,它描述的」切點「是針對類,因此是經過ClassFilter
來判斷。而PointcutAdvisor
能夠針對方法,經過Pointcut
描述切點。這點能夠從canApply()
中看出。spring-boot
public static boolean canApply(Advisor advisor, Class<?> targetClass, boolean hasIntroductions) { if (advisor instanceof IntroductionAdvisor) { //IntroductionAdvisor直接經過classFilter匹配 return ((IntroductionAdvisor) advisor).getClassFilter().matches(targetClass); } else if (advisor instanceof PointcutAdvisor) { //PointcutAdvisor則是經過pointcut,在調用canApply的重載方法實現 PointcutAdvisor pca = (PointcutAdvisor) advisor; return canApply(pca.getPointcut(), targetClass, hasIntroductions); } else { // It doesn't have a pointcut so we assume it applies. return true; } }
找到Advisor以後,剩下的就是建立代理的過程。回到wrapIfNecessary
,建立代理的過程在createProxy()
中。
//建立代理對象 protected Object createProxy( Class<?> beanClass, String beanName, Object[] specificInterceptors, TargetSource targetSource) { if (this.beanFactory instanceof ConfigurableListableBeanFactory) { AutoProxyUtils.exposeTargetClass((ConfigurableListableBeanFactory) this.beanFactory, beanName, beanClass); } //建立代理工廠類,而且拷貝須要的配置 ProxyFactory proxyFactory = new ProxyFactory(); proxyFactory.copyFrom(this); if (!proxyFactory.isProxyTargetClass()) { if (shouldProxyTargetClass(beanClass, beanName)) { proxyFactory.setProxyTargetClass(true); } else { evaluateProxyInterfaces(beanClass, proxyFactory); } } //將攔截器封裝成advisor Advisor[] advisors = buildAdvisors(beanName, specificInterceptors); //設置攔截器和TargetSource proxyFactory.addAdvisors(advisors); proxyFactory.setTargetSource(targetSource); //留給子類根據須要拓展 customizeProxyFactory(proxyFactory); proxyFactory.setFrozen(this.freezeProxy); if (advisorsPreFiltered()) { proxyFactory.setPreFiltered(true); } //建立代理對象 return proxyFactory.getProxy(getProxyClassLoader()); }
上述方法中主要是建立了ProxyFactory
對象,並設置屬性,在經過ProxyFactory
對象建立代理對象。
最後返回的代理對象便取代了原始的bean對象保存在spring容器中待取用。
若是對上述流程圖還有不清楚的地方,能夠參考我畫的流程圖。
經過上述流程咱們瞭解了spring如何根據advisor建立代理。如今咱們要了解的是shiro的advisor:AuthorizationAttributeSourceAdvisor
。
類的關係圖:
從圖中咱們能夠了解到AuthorizationAttributeSourceAdvisor
是一個PointcutAdvisor
。若是看代碼的話你會發現Pointcut
設置的ClassFilter
是TureClassFilter
,也就是說它對任何類判斷都會是經過,只校驗方法是否正確。所以AuthorizationAttributeSourceAdvisor
中最重要的方法就是matches
:
@SuppressWarnings({"unchecked"}) public class AuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor { private static final Logger log = LoggerFactory.getLogger(AuthorizationAttributeSourceAdvisor.class); private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] { RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class }; protected SecurityManager securityManager = null; /** * Create a new AuthorizationAttributeSourceAdvisor. */ public AuthorizationAttributeSourceAdvisor() { //設置通知 setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor()); } public SecurityManager getSecurityManager() { return securityManager; } //設置SecurityManager public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) { this.securityManager = securityManager; } public boolean matches(Method method, Class targetClass) { Method m = method; if ( isAuthzAnnotationPresent(m) ) { return true; } //The 'method' parameter could be from an interface that doesn't have the annotation. //Check to see if the implementation has it. if ( targetClass != null) { try { m = targetClass.getMethod(m.getName(), m.getParameterTypes()); //判斷方法或是類上是否有shiro關注的註解 return isAuthzAnnotationPresent(m) || isAuthzAnnotationPresent(targetClass); } catch (NoSuchMethodException ignored) { //default return value is false. If we can't find the method, then obviously //there is no annotation, so just use the default return value. } } return false; } private boolean isAuthzAnnotationPresent(Class<?> targetClazz) { for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(targetClazz, annClass); if ( a != null ) { return true; } } return false; } private boolean isAuthzAnnotationPresent(Method method) { for( Class<? extends Annotation> annClass : AUTHZ_ANNOTATION_CLASSES ) { Annotation a = AnnotationUtils.findAnnotation(method, annClass); if ( a != null ) { return true; } } return false; } }
除了Advisor的matches
方法外,還須要關注到的是Advisor設置的advise對象:AopAllianceAnnotationsAuthorizingMethodInterceptor
。
我的的理解是AopAllianceAnnotattionsAuthorizingMethodInterceptor
是將shiro框架中的MethodInterceptor
和aopalliance框架中的MethodInterceptor
作了適配,讓shiro的處理過程轉變成aopalliance的MethodIntercetor
的處理過程。然後者是咱們所熟悉的spring的攔截器。
上圖能夠看到同時實現了兩個MethodInterceptor
接口。
AopAllianceAnnotationsAuthorizingMethodInterceptor
代碼相對簡單。
public class AopAllianceAnnotationsAuthorizingMethodInterceptor extends AnnotationsAuthorizingMethodInterceptor implements MethodInterceptor { public AopAllianceAnnotationsAuthorizingMethodInterceptor() { List<AuthorizingAnnotationMethodInterceptor> interceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5); //配置shiro攔截器 AnnotationResolver resolver = new SpringAnnotationResolver(); //we can re-use the same resolver instance - it does not retain state: interceptors.add(new RoleAnnotationMethodInterceptor(resolver)); interceptors.add(new PermissionAnnotationMethodInterceptor(resolver)); interceptors.add(new AuthenticatedAnnotationMethodInterceptor(resolver)); interceptors.add(new UserAnnotationMethodInterceptor(resolver)); interceptors.add(new GuestAnnotationMethodInterceptor(resolver)); setMethodInterceptors(interceptors); } /** * Creates a {@link MethodInvocation MethodInvocation} that wraps an * {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} instance, * enabling Shiro Annotations in <a href="http://aopalliance.sourceforge.net/">AOP Alliance</a> environments * (Spring, etc). * * @param implSpecificMethodInvocation AOP Alliance {@link org.aopalliance.intercept.MethodInvocation MethodInvocation} * @return a Shiro {@link MethodInvocation MethodInvocation} instance that wraps the AOP Alliance instance. */ protected org.apache.shiro.aop.MethodInvocation createMethodInvocation(Object implSpecificMethodInvocation) { final MethodInvocation mi = (MethodInvocation) implSpecificMethodInvocation; return new org.apache.shiro.aop.MethodInvocation() { public Method getMethod() { return mi.getMethod(); } public Object[] getArguments() { return mi.getArguments(); } public String toString() { return "Method invocation [" + mi.getMethod() + "]"; } public Object proceed() throws Throwable { return mi.proceed(); } public Object getThis() { return mi.getThis(); } }; } /** * Simply casts the method argument to an * {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} and then * calls <code>methodInvocation.{@link org.aopalliance.intercept.MethodInvocation#proceed proceed}()</code> * * @param aopAllianceMethodInvocation the {@link org.aopalliance.intercept.MethodInvocation org.aopalliance.intercept.MethodInvocation} * @return the {@link org.aopalliance.intercept.MethodInvocation#proceed() org.aopalliance.intercept.MethodInvocation.proceed()} method call result. * @throws Throwable if the underlying AOP Alliance <code>proceed()</code> call throws a <code>Throwable</code>. */ protected Object continueInvocation(Object aopAllianceMethodInvocation) throws Throwable { MethodInvocation mi = (MethodInvocation) aopAllianceMethodInvocation; return mi.proceed(); } //經過spring中的攔截器機制發起攔截,並將處理轉換成shiro的攔截器處理過程,是一個適配的過程 public Object invoke(MethodInvocation methodInvocation) throws Throwable { //將spring的MethodInvocation轉換成shiro的MethodInvocation對象 org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation); //調用AuthorizingMethodInterceptor的invoke方法 return super.invoke(mi); } }
AuthorizingMethodInterceptor
的invoke則會調用asserAuthorized
方法。
public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport { //攔截器方法被調用 public Object invoke(MethodInvocation methodInvocation) throws Throwable { assertAuthorized(methodInvocation); return methodInvocation.proceed(); } //受權判斷,交給子類實現 protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException; }
AnnotationAuthorizingMethodInterceptor
方法實現了assertAuthorized
方法,遍歷其配置的AuthorizingAnnotationMethodInterceptor
對象,若是匹配則進行驗證。
public abstract class AnnotationsAuthorizingMethodInterceptor extends AuthorizingMethodInterceptor { /** * The method interceptors to execute for the annotated method. */ protected Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors; public AnnotationsAuthorizingMethodInterceptor() { //配置默認的權限認證攔截器 methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5); methodInterceptors.add(new RoleAnnotationMethodInterceptor()); methodInterceptors.add(new PermissionAnnotationMethodInterceptor()); methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor()); methodInterceptors.add(new UserAnnotationMethodInterceptor()); methodInterceptors.add(new GuestAnnotationMethodInterceptor()); } public Collection<AuthorizingAnnotationMethodInterceptor> getMethodInterceptors() { return methodInterceptors; } public void setMethodInterceptors(Collection<AuthorizingAnnotationMethodInterceptor> methodInterceptors) { this.methodInterceptors = methodInterceptors; } //遍歷全部權限認證攔截器,若是攔截器支持,則使用攔截器認證 protected void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException { //default implementation just ensures no deny votes are cast: Collection<AuthorizingAnnotationMethodInterceptor> aamis = getMethodInterceptors(); if (aamis != null && !aamis.isEmpty()) { for (AuthorizingAnnotationMethodInterceptor aami : aamis) { if (aami.supports(methodInvocation)) { aami.assertAuthorized(methodInvocation); } } } } }
而權限認證攔截器則是將具體認證過程委託給內部的Handler
對象處理。所以攔截器處理的過程大體以下:
AopAllianceAnnotationAuthorizingMethodInterceptor
的invoke
方法被調用assertAuthorized()
assertAuthorized
方法AuthorizingAnnotationHandler
處理RoleAnnotationHandler
爲例,它會在本身的assertAuthorized
方法中校驗Subject
對象的Role
和@RequiredRole
中要求的是否一致,不一致則會拋出異常,攔截器不在往下走,由於也沒法進入到被攔截的方法裏。Shiro權限認證的過程是經過AOP動態代理實現的。至關於在Spring中配置了一個用於權限認證的攔截器,攔截擁有指定註解(@RequiresAuthentication
,@RequiresUser
,@RequiresGuest
,@RequiresRoles
,@RequiresPermissions
)的方法。