Shiro权限注解原理

Posted fzsyw

tags:

篇首语:本文由小常识网(cha138.com)小编为大家整理,主要介绍了Shiro权限注解原理相关的知识,希望对你有一定的参考价值。

概述

前不久刚学会使用权限注解(),开始思索了一番。最开始猜测实现方式是注解@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的理解。

以上是关于Shiro权限注解原理的主要内容,如果未能解决你的问题,请参考以下文章

Shiro 权限注解

shiro注解权限控制-5个权限注解

shiro框架 RequiresPermissions注解怎么动态配置

shiro框架 RequiresPermissions注解怎么动态配置

springboot + shiro 权限注解请求乱码解决统一异常处理

源码分析 shiro注解授权