目录
1.Spring Aop嵌入点解析
2.Shiro框架Aop切面逻辑解析
2.1 通过注解实现切点
2.2 通过增强逻辑执行校验过程
2.2.1 增强实现类AopAllianceAnnotationsAuthorizingMethodInterceptor
2.2.1.1 类图解析
2.2.1.2 实现增强方法
2.2.1.3 Shiro校验逻辑实现
2.2.1.3.1 获取Shiro自定义方法拦截器
2.2.1.3.1.1 Shiro自定义方法拦截器类图
2.2.1.3.1.2 Shiro自定义方法拦截器创建
2.2.1.3.2 supports方法过滤方法拦截器
2.2.1.3.3 方法拦截器执行
Shiro作为一款比较流行的登录认证、访问控制安全框架,被广泛应用在程序员社区;Shiro登录验证、访问控制、Session管理等流程内部都是委托给SecurityManager安全管理器来完成的,SecurityManager安全管理器前面文章已经进行了详细解析,详见:Shiro框架:Shiro SecurityManager安全管理器解析;在此基础上,前面文章已对Shiro用户登录认证流程(详见:Shiro框架:Shiro登录认证流程源码解析)进行了源码跟踪,Shiro框架:Shiro用户访问控制鉴权流程-内置过滤器方式源码解析 也对用户访问控制鉴权流程-内置过滤器方式进行了详细解析,本篇文章继续对用户访问控制鉴权流程-Aop注解方式进行源码解析,了解不同的使用方式以便更好的应用到实际项目中;
想要深入了解Shiro框架整体原理,可移步:
Shiro框架:ShiroFilterFactoryBean过滤器源码解析、
Shiro框架:Shiro内置过滤器源码解析
Shiro的权限校验逻辑是嵌入到Spring Aop的增强逻辑中进行执行的,下面先看一下Shiro与Spring Aop是如何结合的,也即对Shiro校验逻辑在Spring Aop的嵌入点进行分析;
1.Spring Aop嵌入点解析
Shiro的校验逻辑应用到了Aop框架的抽象类StaticMethodMatcherPointcutAdvisor,该类的继承层次结构如下:
可以看出,其实现了切点Pointcut和切面Advisor 2个顶层接口,并通过实现方法匹配接口MethodMatcher定位切点,进而执行增强逻辑;
在Shiro框架中,实现StaticMethodMatcherPointcutAdvisor的具体子类为AopAllianceAnnotationsAuthorizingMethodInterceptor,其同时实现了Shiro框架的切点和增强逻辑,下面进行具体分析;
对Spring Aop的实现逻辑感兴趣,可以参考之前的文章:
Spring源码:Aop中@Aspect切面的解析代理过程
Spring源码:Aop源码分析
2.Shiro框架Aop切面逻辑解析
Shiro框架的Aop切面逻辑主要包含了2部分:
切点(Pointcut):Shiro如何通过自定义注解方式捕捉切点
增强逻辑(Advice):Shiro如何通过增强逻辑执行内部校验过程
Shiro的切点和增强逻辑定义在AuthorizationAttributeSourceAdvisor切面类中的,其类图如下:
可以看出,其继承了StaticMethodMatcherPointcutAdvisor类,并植入了切点和增强逻辑,下面分别进行解析;
2.1 通过注解实现切点
AuthorizationAttributeSourceAdvisor类中,是通过实现接口MethodMatcher的方法matches引入切点的,具体实现如下:
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()); 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; }
如上,通过判断Class级别和Method级别是否包含注解AUTHZ_ANNOTATION_CLASSES,来判断是否命中切点;
这里AUTHZ_ANNOTATION_CLASSES的定义如下:
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES = new Class[] { RequiresPermissions.class, RequiresRoles.class, RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class };
这里的注解都是Shiro框架内置的自定义注解,比如RequiresPermissions注解用于用户权限校验,RequiresRoles注解用于用户角色校验,RequiresAuthentication注解校验用户登录状态;
因此Shiro通过自定义注解的方式实现了Aop切点;
2.2 通过增强逻辑执行校验过程
Shiro的增强逻辑同样在类AuthorizationAttributeSourceAdvisor中,在构造方法中通过setAdvice指定了增强实现类,如下:
/** * Create a new AuthorizationAttributeSourceAdvisor. */ public AuthorizationAttributeSourceAdvisor() { setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor()); }
在类AopAllianceAnnotationsAuthorizingMethodInterceptor中,实现了是如何进行增强逻辑处理的,下面进行具体分析;
2.2.1 增强实现类AopAllianceAnnotationsAuthorizingMethodInterceptor
2.2.1.1 类图解析
AopAllianceAnnotationsAuthorizingMethodInterceptor类图如下,
左侧类层次结构是Shiro内部的实现,用于实现Shiro的增强逻辑
右侧类层次结构是Spring Aop中实现增强的切入点,这里实现了MethodInterceptor方法拦截器接口,其内部通过引入的invoke方式执行增强逻辑:
public interface MethodInterceptor extends Interceptor { /** * Implement this method to perform extra treatments before and * after the invocation. Polite implementations would certainly * like to invoke {@link Joinpoint#proceed()}. * @param invocation the method invocation joinpoint * @return the result of the call to {@link Joinpoint#proceed()}; * might be intercepted by the interceptor * @throws Throwable if the interceptors or the target object * throws an exception */ Object invoke(MethodInvocation invocation) throws Throwable; }
下面主要分析下AopAllianceAnnotationsAuthorizingMethodInterceptor是如何实现invoke方法的;
2.2.1.2 实现增强方法
增强方法的实现如下:
public Object invoke(MethodInvocation methodInvocation) throws Throwable { org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation); return super.invoke(mi); }
这里创建了Shiro内部自定义的MethodInvocation,然后调用父类invoke方法,createMethodInvocation实现如下:
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(); } }; }
父类AuthorizingMethodInterceptor的invoke方法实现如下:
public abstract class AuthorizingMethodInterceptor extends MethodInterceptorSupport { /** * Invokes the specified method (<code>methodInvocation.{@link org.apache.shiro.aop.MethodInvocation#proceed proceed}()</code> * if authorization is allowed by first * calling {@link #assertAuthorized(org.apache.shiro.aop.MethodInvocation) assertAuthorized}. */ public Object invoke(MethodInvocation methodInvocation) throws Throwable { assertAuthorized(methodInvocation); return methodInvocation.proceed(); } /** * Asserts that the specified MethodInvocation is allowed to continue by performing any necessary authorization * (access control) checks first. * @param methodInvocation the <code>MethodInvocation</code> to invoke. * @throws AuthorizationException if the <code>methodInvocation</code> should not be allowed to continue/execute. */ protected abstract void assertAuthorized(MethodInvocation methodInvocation) throws AuthorizationException; }
可以看到,invoke方法内,首先调用assertAuthorized方法执行增强逻辑(Shiro框架中即为自定义校验逻辑),校验通过,再继续执行目标方法,下面重点分析下assertAuthorized的具体实现逻辑;
2.2.1.3 Shiro校验逻辑实现
assertAuthorized方法的实现是在类AnnotationsAuthorizingMethodInterceptor中,如下:
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); } } } }
如上,这里主要包含了3个部分:
- 首先获取Shiro自定义的方法拦截器
- 然后通过supports方法过滤方法拦截器
- 最后调用方法拦截器的assertAuthorized方法实现拦截
下面进行具体分析:
2.2.1.3.1 获取Shiro自定义方法拦截器
Shiro自定义方法拦截器保存在了成员变量methodInterceptors中,其具体赋值是在AopAllianceAnnotationsAuthorizingMethodInterceptor构造函数中,如下:
public AopAllianceAnnotationsAuthorizingMethodInterceptor() { List<AuthorizingAnnotationMethodInterceptor> interceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5); //use a Spring-specific Annotation resolver - Spring's AnnotationUtils is nicer than the //raw JDK resolution process. 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); }
下面首先对Shiro自定义方法拦截器进行分析;
2.2.1.3.1.1 Shiro自定义方法拦截器类图
如上,实例化了多个Shiro自定义方法拦截器,整体类图如下:
可以看出这里的PermissionAnnotationMethodInterceptor方法拦截器等也是实现了Shiro MethodInterceptor接口,并且通过组合的方式注入到了前面的增强类AopAllianceAnnotationsAuthorizingMethodInterceptor中;
2.2.1.3.1.2 Shiro自定义方法拦截器创建
在上面Shiro多个自定义方法拦截器创建时,这里以PermissionAnnotationMethodInterceptor为例分析,其创建过程如下:
public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) { super( new PermissionAnnotationHandler(), resolver); }
这里绑定了注解处理器PermissionAnnotationHandler,注解处理器的整体类图图示如下:
在PermissionAnnotationHandler内部通过构造函数绑定了注解@RequiresPermissions,如下:
public class PermissionAnnotationHandler extends AuthorizingAnnotationHandler { /** * Default no-argument constructor that ensures this handler looks for * {@link org.apache.shiro.authz.annotation.RequiresPermissions RequiresPermissions} annotations. */ public PermissionAnnotationHandler() { super(RequiresPermissions.class); } //…… }
另外,我们可以看到实例化时注入了注解解析器,这里实际为SpringAnnotationResolver,主要完成对指定注解的解析工作,实现如下:
public class SpringAnnotationResolver implements AnnotationResolver { public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) { Method m = mi.getMethod(); Annotation a = AnnotationUtils.findAnnotation(m, clazz); if (a != null) return a; //The MethodInvocation's method object could be a method defined in an interface. //However, if the annotation existed in the interface's implementation (and not //the interface itself), it won't be on the above method object. Instead, we need to //acquire the method representation from the targetClass and check directly on the //implementation itself: Class<?> targetClass = mi.getThis().getClass(); m = ClassUtils.getMostSpecificMethod(m, targetClass); a = AnnotationUtils.findAnnotation(m, clazz); if (a != null) return a; // See if the class has the same annotation return AnnotationUtils.findAnnotation(mi.getThis().getClass(), clazz); } }
归纳总结,在PermissionAnnotationMethodInterceptor创建过程中,注入了PermissionAnnotationHandler和SpringAnnotationResolver:
- PermissionAnnotationHandler注解处理器:绑定了注解@RequiresPermissions
- SpringAnnotationResolver注解解析器:用于判断目标方法是否存在指定注解
2.2.1.3.2 supports方法过滤方法拦截器
supports方法的实现是在父类AnnotationMethodInterceptor中,如下:
public boolean supports(MethodInvocation mi) { return getAnnotation(mi) != null; } protected Annotation getAnnotation(MethodInvocation mi) { return getResolver().getAnnotation(mi, getHandler().getAnnotationClass()); }
这里通过getHandler().getAnnotationClass()方法获取绑定的注解,同样以PermissionAnnotationMethodInterceptor为例,getHandler()即为PermissionAnnotationHandler,getAnnotationClass()即为@RequiresPermissions;
这里getResolver()即为前面创建的SpringAnnotationResolver,通过getAnnotation方法获取目标方法上的@RequiresPermissions注解,并判断该注解是否存在,存在则匹配成功,然后继续调用方法拦截器进行执行;
2.2.1.3.3 方法拦截器执行
通过调用assertAuthorized方法执行拦截逻辑,以PermissionAnnotationMethodInterceptor为例,其实现是在父类AuthorizingAnnotationMethodInterceptor中实现的,如下:
public void assertAuthorized(MethodInvocation mi) throws AuthorizationException { try { ((AuthorizingAnnotationHandler)getHandler()).assertAuthorized(getAnnotation(mi)); } catch(AuthorizationException ae) { // Annotation handler doesn't know why it was called, so add the information here if possible. // Don't wrap the exception here since we don't want to mask the specific exception, such as // UnauthenticatedException etc. if (ae.getCause() == null) ae.initCause(new AuthorizationException("Not authorized to invoke method: " + mi.getMethod())); throw ae; } }
这里又委托给注解处理器,即PermissionAnnotationHandler,调用其方法assertAuthorized执行校验逻辑,参数为匹配到的目标方法上的注解@RequiresPermissions,具体实现如下:
public void assertAuthorized(Annotation a) throws AuthorizationException { if (!(a instanceof RequiresPermissions)) return; RequiresPermissions rpAnnotation = (RequiresPermissions) a; String[] perms = getAnnotationValue(a); Subject subject = getSubject(); if (perms.length == 1) { subject.checkPermission(perms[0]); return; } if (Logical.AND.equals(rpAnnotation.logical())) { getSubject().checkPermissions(perms); return; } if (Logical.OR.equals(rpAnnotation.logical())) { // Avoid processing exceptions unnecessarily - "delay" throwing the exception by calling hasRole first boolean hasAtLeastOnePermission = false; for (String permission : perms) if (getSubject().isPermitted(permission)) hasAtLeastOnePermission = true; // Cause the exception if none of the role match, note that the exception message will be a bit misleading if (!hasAtLeastOnePermission) getSubject().checkPermission(perms[0]); } }
这里获取到了注解上配置的访问目标方法所需要的权限,也获取了当前登录用户Subject,然后调用其checkPermission方法执行权限校验,实现如下:
public void checkPermission(String permission) throws AuthorizationException { assertAuthzCheckPossible(); securityManager.checkPermission(getPrincipals(), permission); }
可以看到,这里也是委托给了安全管理器执行权限校验,并获取了当前登录用户的账户名Principals,进一步跟踪如下:
public void checkPermission(PrincipalCollection principals, String permission) throws AuthorizationException { this.authorizer.checkPermission(principals, permission); }
这里继续委托给权限校验器authorizer执行权限校验,这里具体类为ModularRealmAuthorizer,继续跟踪如下:
/** * If !{@link #isPermitted(org.apache.shiro.subject.PrincipalCollection, Permission) isPermitted(permission)}, throws * an <code>UnauthorizedException</code> otherwise returns quietly. */ public void checkPermission(PrincipalCollection principals, Permission permission) throws AuthorizationException { assertRealmsConfigured(); if (!isPermitted(principals, permission)) { throw new UnauthorizedException("Subject does not have permission [" + permission + "]"); } }
如上会校验权限组件Realm已配置,并委托给isPermitted方法校验当前用户是否具备指定权限,没有配置指定权限时,会抛出权限校验异常UnauthorizedException;
后续isPermitted方法的处理流程可以参见的Shiro框架:Shiro用户访问控制鉴权流程-内置过滤器方式源码解析 的章节“3.2.1 isPermitted权限校验”。
至此,Shiro用户访问控制鉴权流程-Aop注解方式源码解析完成,Over~~