zoukankan      html  css  js  c++  java
  • 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的理解。

  • 相关阅读:
    CentOS6.0/RedHat Server 6.4安装配置过程 详细图解!
    关于Haproxy安装和配置:负载配置【haproxy.cfg】问题记录
    菜鸟学习Struts——bean标签库
    2013——2014总结
    高效程序员的45个习惯读书 ——敏捷开发修炼之道笔记之态度决定一切
    Hive深入浅出
    Java从入门到精通——调错篇之SVN 出现 Loced错误
    考试系统优化——准备工作
    深入解析:分布式系统的事务处理经典问题及模型(转载分享)
    黑客攻击 UVa11825
  • 原文地址:https://www.cnblogs.com/fzsyw/p/11384551.html
Copyright © 2011-2022 走看看