Shiro框架:Shiro用户访问控制鉴权流程-Aop注解方式源码解析

目录

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个部分:

  1. 首先获取Shiro自定义的方法拦截器
  2. 然后通过supports方法过滤方法拦截器
  3. 最后调用方法拦截器的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:

  1. PermissionAnnotationHandler注解处理器:绑定了注解@RequiresPermissions
  2. 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~~