欢迎您访问程序员文章站本站旨在为大家提供分享程序员计算机编程知识!
您现在的位置是: 首页  >  IT编程

shiro中权限注解原理分析

程序员文章站 2022-03-25 16:15:14
概述 前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@Aspect,具体实现方式类似如下所示(切面记录审计日志)。后来发现并非如此,所以特地分析一下源码。 权限注解的源码分析 这个类实现了 接口,当 读取所有的Bean配置信息后,这个类将扫描上下文,寻找所有的 (一个 是一个 ......

概述

前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@aspect,具体实现方式类似如下所示(切面记录审计日志)。后来发现并非如此,所以特地分析一下源码。

@component
@aspect
public class auditlogaspectconfig {
    @pointcut("@annotation(com.ygsoft.ecp.mapp.basic.audit.annotation.auditlog) || @annotation(com.ygsoft.ecp.mapp.basic.audit.annotation.auditlogs)")
    public void pointcut() {        
    }

    @after(value="pointcut()")
    public void after(joinpoint joinpoint) {
        //执行的逻辑
    }
    ...
}

权限注解的源码分析

defaultadvisorautoproxycreator这个类实现了beanprocessor接口,当applicationcontext读取所有的bean配置信息后,这个类将扫描上下文,寻找所有的advistor(一个advisor是一个切入点和一个通知的组成),将这些advisor应用到所有符合切入点的bean中。

@configuration
public class shiroannotationprocessorconfiguration extends abstractshiroannotationprocessorconfiguration{
    @bean
    @dependson("lifecyclebeanpostprocessor")
    protected defaultadvisorautoproxycreator defaultadvisorautoproxycreator() {
        return super.defaultadvisorautoproxycreator();
    }

    @bean
    protected authorizationattributesourceadvisor authorizationattributesourceadvisor(securitymanager securitymanager) {
        return super.authorizationattributesourceadvisor(securitymanager);
    }

}

authorizationattributesourceadvisor继承了staticmethodmatcherpointcutadvisor,如下代码所示,只匹配五个注解,也就是说只对这五个注解标注的类或者方法增强。staticmethodmatcherpointcutadvisor是静态方法切点的抽象基类,默认情况下它匹配所有的类。staticmethodmatcherpointcut包括两个主要的子类分别是namematchmethodpointcutabstractregexpmethodpointcut,前者提供简单字符串匹配方法前面,而后者使用正则表达式匹配方法前面。动态方法切点:dynamicmethodmatcerpointcut是动态方法切点的抽象基类,默认情况下它匹配所有的类,而且也已经过时,建议使用defaultpointcutadvisordynamicmethodmatcherpointcut动态方法代替。另外还需关注构造器中的传入的aopallianceannotationsauthorizingmethodinterceptor

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;

    public authorizationattributesourceadvisor() {
        setadvice(new aopallianceannotationsauthorizingmethodinterceptor());
    }

    public securitymanager getsecuritymanager() {
        return 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;
        }
        
        if ( targetclass != null) {
            try {
                m = targetclass.getmethod(m.getname(), m.getparametertypes());
                if ( isauthzannotationpresent(m) ) {
                    return true;
                }
            } catch (nosuchmethodexception ignored) {
                
            }
        }

        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;
    }

}

aopallianceannotationsauthorizingmethodinterceptor在初始化时,interceptors添加了5个方法拦截器(都继承自authorizingannotationmethodinterceptor),这5个拦截器分别对5种权限验证的方法进行拦截,执行invoke方法。

public class aopallianceannotationsauthorizingmethodinterceptor
        extends annotationsauthorizingmethodinterceptor implements methodinterceptor {

    public aopallianceannotationsauthorizingmethodinterceptor() {
        list<authorizingannotationmethodinterceptor> interceptors =
                new arraylist<authorizingannotationmethodinterceptor>(5);
        annotationresolver resolver = new springannotationresolver();
        
        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);
    }
    
    public object invoke(methodinvocation methodinvocation) throws throwable {
        org.apache.shiro.aop.methodinvocation mi = createmethodinvocation(methodinvocation);
        return super.invoke(mi);
    }
    ...
}

aopallianceannotationsauthorizingmethodinterceptor的invoke方法,又会调用超类authorizingmethodinterceptor的invoke方法,在该方法中先执行assertauthorized方法,进行权限校验,校验不通过,抛出authorizationexception异常,中断方法;校验通过,则执行methodinvocation.proceed(),该方法也就是被拦截并且需要权限校验的方法。

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;
}

assertauthorized方法最终执行的还是authorizingannotationmethodinterceptor.assertauthorized,而authorizingannotationmethodinterceptor有5中的具体的实现类(roleannotationmethodinterceptor, permissionannotationmethodinterceptor, authenticatedannotationmethodinterceptor, userannotationmethodinterceptor, guestannotationmethodinterceptor)。

public abstract class annotationsauthorizingmethodinterceptor extends   authorizingmethodinterceptor {
  
    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);
                }
            }
        }
    }
    ...
}

authorizingannotationmethodinterceptor的assertauthorized,首先从子类获取authorizingannotationhandler,再调用该实现类的assertauthorized方法。

public abstract class authorizingannotationmethodinterceptor extends annotationmethodinterceptor
{

    public authorizingannotationmethodinterceptor( authorizingannotationhandler handler ) {
        super(handler);
    }

    public authorizingannotationmethodinterceptor( authorizingannotationhandler handler,
                                                   annotationresolver resolver) {
        super(handler, resolver);
    }

    public object invoke(methodinvocation methodinvocation) throws throwable {
        assertauthorized(methodinvocation);
        return methodinvocation.proceed();
    }

    public void assertauthorized(methodinvocation mi) throws authorizationexception {
        try {
            ((authorizingannotationhandler)gethandler()).assertauthorized(getannotation(mi));
        }
        catch(authorizationexception ae) {
            if (ae.getcause() == null) ae.initcause(new authorizationexception("not authorized to invoke method: " + mi.getmethod()));
            throw ae;
        }         
    }
}

现在分析其中一种实现类permissionannotationmethodinterceptor,也是用的最多的,但是这个类的实际代码很少,很明显上述分析的gethandler在permissionannotationmethodinterceptor中返回值为permissionannotationhandler

public class permissionannotationmethodinterceptor extends authorizingannotationmethodinterceptor {

    public permissionannotationmethodinterceptor() {
        super( new permissionannotationhandler() );
    }

 
    public permissionannotationmethodinterceptor(annotationresolver resolver) {
        super( new permissionannotationhandler(), resolver);
    }
}

permissionannotationhandler类中,终于发现实际的检验逻辑,还是调用的subject.checkpermission()进行校验。

public class permissionannotationhandler extends authorizingannotationhandler {

    public permissionannotationhandler() {
        super(requirespermissions.class);
    }

    protected string[] getannotationvalue(annotation a) {
        requirespermissions rpannotation = (requirespermissions) a;
        return rpannotation.value();
    }

    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())) {
            boolean hasatleastonepermission = false;
            for (string permission : perms) if (getsubject().ispermitted(permission)) hasatleastonepermission = true;
            if (!hasatleastonepermission) getsubject().checkpermission(perms[0]);
            
        }
    }
}

实现类似编程式aop

定义一个注解

@target({elementtype.method})
@retention(retentionpolicy.runtime)
public @interface log {
    string value() default "";
}

继承staticmethodmatcherpointcutadvisor类,并实现相关的方法。

@suppresswarnings("serial")
@component
public class helloadvisor extends staticmethodmatcherpointcutadvisor{
    
    public helloadvisor() {
        setadvice(new logmethodinterceptor());
    }

    public boolean matches(method method, class targetclass) {
        method m = method;
        if ( isauthzannotationpresent(m) ) {
            return true;
        }

        if ( targetclass != null) {
            try {
                m = targetclass.getmethod(m.getname(), m.getparametertypes());
                return isauthzannotationpresent(m);
            } catch (nosuchmethodexception ignored) {
               
            }
        }
        return false;
    }

    private boolean isauthzannotationpresent(method method) {
        annotation a = annotationutils.findannotation(method, log.class);
        return a!= null;
    }
}

实现methodinterceptor接口,定义切面处理的逻辑

public class logmethodinterceptor implements methodinterceptor{

    public object invoke(methodinvocation invocation) throws throwable {
        log log = invocation.getmethod().getannotation(log.class);
        system.out.println("log: "+log.value());
        return invocation.proceed();    
    }
}

定义一个测试类,并添加log注解

@component
public class testhello {

    @log("test log")
    public string say() {
        return "ss";
    }
}

编写启动类,并且配置defaultadvisorautoproxycreator

@configuration
public class testboot {

    public static void main(string[] args) {
        applicationcontext ctx = new annotationconfigapplicationcontext("com.fzsyw.test");  
        testhello th = ctx.getbean(testhello.class);
        system.out.println(th.say());
    }
    
    @bean
    public defaultadvisorautoproxycreator defaultadvisorautoproxycreator(){
        defaultadvisorautoproxycreator da = new defaultadvisorautoproxycreator();
        da.setproxytargetclass(true);
        return da;
    }
}

最终打印的结果如下,证明编程式的aop生效。

log: test log
ss

总结与思考

shiro的注解式权限,使用确实方便,通过源码也分析了它的实现原理,比较核心的是配置defaultadvisorautoproxycreator和继承staticmethodmatcherpointcutadvisor。其中的5中权限注解,使用了了统一一套代码,用到了大量的模板模式,方便扩展。最后自己也简单做了一个小例子,加深对编程式aop的理解。