上一篇文章介紹了shiro在spring-boot中通過filter實現authentication流程(通過設置filterMaps也可以達到authorization的目的);這篇文章主要介紹spring通過AOP的方式實現shiro的authorization的流程。
ShiroAnnotationProcessorAutoConfiguration
shiro-spring-boot-web-starter
除了自身在META-INF中定義了ShiroWebAutoConfiguration
和ShiroWebFilterConfiguration
外,還在pom
文件中引用了shiro-spring-boot-stater
。而后者在自己的META-INF
文件中又定義了三個配置類:
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的方式在執行某些方法前先進行權限校驗。
DefaultAdvisorAutoProxyCreator和創建代理的流程
DefaultAdvisorAutoProxyCreator
是spring框架提供的用來創建代理的類。可以通過這個類理清spring創建代理的流程。先了解DefaultAdvisorAutoProxyCreator
的類繼承關系。圖中刪除了部分繼承關系,只保留了最主要的內容:
從接口的繼承關系中可以看到,該類的處理可能處於類的實例化前后(Instantiation)和初始化前后(Initialization)。
下面的分析將以Bean的創建流程為順序。
- 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;
}
- 創建Bean實例:
如果在postProcessBeanBeforeInstantication
中已經創建了Bean的代理對象,則會跳過createBean
的過程。 - 實例化后置處理
postProcessAfterInstantication()
:
該方法返回boolean
型的值,決定是否繼續執行是剩下的InstantationAwareBeanPostProcessor
。 - 初始化前置處理
postProcessBeforeInitialization()
:這里不對bean做任務處理直接返回。
@Override
public Object postProcessBeforeInitialization(Object bean, String beanName) {
return bean;
}
- bean初始化,這個階段可能會設置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
。
//獲取可以應用在該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()
。
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()
中看出。
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容器中待取用。
如果對上述流程圖還有不清楚的地方,可以參考我畫的流程圖。
shiro生命的Advisor:AuthorizationAttributeSourceAdvisor
通過上述流程我們了解了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
)的方法。