shiro @RequiresPermissions设计与 实现

本篇主要以@RequiresPermissions

注解为例,讲解shiro中如何设计与实现

首先定义注解

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresPermissions {

    /**
     * The permission string which will be passed to {@link org.apache.shiro.subject.Subject#isPermitted(String)}
     * to determine if the user is allowed to invoke the code protected by this annotation.
     */
    String[] value();
    
    /**
     * The logical operation for the permission checks in case multiple permissions are specified. AND is the default
     * @since 1.1.0
     */
    Logical logical() default Logical.AND; 

}

可以看出value可以有多个值,logical枚举有or或者and两种,默认and,只有所有value都满足才行

抽象 注解处理类父类,可以看出只是简单的对注解类型的read,write,获取subject操作

接下来定义了关于权限相关的抽象类父类AuthorizingAnnotationHandler,里面定义了处理注解的抽象方法assertAuthorized方法,子类实现。

permission注解实现类

可以看到实现了assertAuthorized方法,获取注解中的信息,利用subject的isPermitted方法判断是否有权限,否则抛出异常

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

注解处理器好了,下面来看看注解解析器

接口 AnnotationResolver,判断在一个方法上是否存在该注解

默认实现类为:

public Annotation getAnnotation(MethodInvocation mi, Class<? extends Annotation> clazz) {
        if (mi == null) {
            throw new IllegalArgumentException("method argument cannot be null");
        }
        Method m = mi.getMethod();
        if (m == null) {
            String msg = MethodInvocation.class.getName() + " parameter incorrectly constructed.  getMethod() returned null";
            throw new IllegalArgumentException(msg);

        }
        Annotation annotation = m.getAnnotation(clazz);
        if (annotation == null ) {
            Object miThis = mi.getThis();
            //SHIRO-473 - miThis could be null for static methods, just return null
            annotation = miThis != null ? miThis.getClass().getAnnotation(clazz) : null;
        }
        return annotation;
    }

下面该定义拦截器接口,在执行注解标注的方法之前先执行MethodInvocation利用Subject验证是否有权限执行该方法

抽象子类MethodInterceptorSupport增加了获取subject的方法

抽象子类AnnotationMethodInterceptor 有两个实例字段,一个注解处理器,一个注解解析器。利用注解处理器获取(MethodInvocation中的注解和注解处理器中的注解作为参数)获取注解,能获取到说明支持该注解,否则该拦截器不支持

该注解。

抽象类 AuthorizingAnnotationMethodInterceptor 继承AnnotationMethodInterceptor

   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) {
            // 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;
        }         
    }

那么这些关于权限的注解拦截器都是怎么被调用的那?shiro又专门有类继承自拦截器支持类:

抽象类 AnnotationsAuthorizingMethodInterceptor 里面包含了所有关于权限的注解,循环遍历执行各个拦截器

public AnnotationsAuthorizingMethodInterceptor() {
        methodInterceptors = new ArrayList<AuthorizingAnnotationMethodInterceptor>(5);
        methodInterceptors.add(new RoleAnnotationMethodInterceptor());
        methodInterceptors.add(new PermissionAnnotationMethodInterceptor());
        methodInterceptors.add(new AuthenticatedAnnotationMethodInterceptor());
        methodInterceptors.add(new UserAnnotationMethodInterceptor());
        methodInterceptors.add(new GuestAnnotationMethodInterceptor());
    }

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

那么如何调用那?

1:这里首先介绍aop的实现者aspectj的支持

/**
     * Performs the method interception of the before advice at the specified joint point.
     *
     * @param aJoinPoint The joint point to intercept.
     * @throws Throwable If an error occurs berforming the method invocation.
     */
    protected void performBeforeInterception(JoinPoint aJoinPoint) throws Throwable {
       

        // 1. Adapt the join point into a method invocation
        BeforeAdviceMethodInvocationAdapter mi = BeforeAdviceMethodInvocationAdapter.createFrom(aJoinPoint);

        // 2. Delegate the authorization of the method call to the super class
        super.invoke(mi);
    }

可以看出在这里调用了父类的invoke方法,BeforeAdviceMethodInvocationAdapter 就是接口MethodInvocation的实现类

那又是在哪里调用的AspectjAnnotationsAuthorizingMethodInterceptor那?就是下面这个切面类:

@Aspect()
public class ShiroAnnotationAuthorizingAspect {

    private static final String pointCupExpression =
            "execution(@org.apache.shiro.authz.annotation.RequiresAuthentication * *(..)) || " +
                    "execution(@org.apache.shiro.authz.annotation.RequiresGuest * *(..)) || " +
                    "execution(@org.apache.shiro.authz.annotation.RequiresPermissions * *(..)) || " +
                    "execution(@org.apache.shiro.authz.annotation.RequiresRoles * *(..)) || " +
                    "execution(@org.apache.shiro.authz.annotation.RequiresUser * *(..))";

    @Pointcut(pointCupExpression)
    public void anyShiroAnnotatedMethod(){}

    @Pointcut(pointCupExpression)
    void anyShiroAnnotatedMethodCall(JoinPoint thisJoinPoint) {
    }

    private AspectjAnnotationsAuthorizingMethodInterceptor interceptor =
            new AspectjAnnotationsAuthorizingMethodInterceptor();

    @Before("anyShiroAnnotatedMethodCall(thisJoinPoint)")
    public void executeAnnotatedMethod(JoinPoint thisJoinPoint) throws Throwable {
        interceptor.performBeforeInterception(thisJoinPoint);
    }
}

所以说,凡是遇到带有上面定义的权限注解的方法都会执行一次performBeforeInterception方法,而此方法又调用了父类的invoke,invoke里面又遍历调用了所有的权限注解处理类,去判断是否权限执行该方法。

2:spring支持(其实不止spring,shiro注解能在按照aop联盟制定的规则实现的任何环境下执行)

首先看类:AopAllianceAnnotationsAuthorizingMethodInterceptor

看构造方法中把所有的注解处理器放在父类的methodInterceptors中

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

然后实现invoke方法:

public Object invoke(MethodInvocation methodInvocation) throws Throwable {
//把aop联盟中的MethodInvocation转变成shiro中的MethodInvocation        
org.apache.shiro.aop.MethodInvocation mi = createMethodInvocation(methodInvocation);
        return super.invoke(mi);
    }

接着调用父类invoke,过程和上面aspectj一样遍历所有注解处理器

接着切面类 AuthorizationAttributeSourceAdvisor 整合advice(通知或者增强)和pointcut(切点,按某些条件从jointpoint连接点中查出来的符合条件的切点)

构造方法中设置advice

 public AuthorizationAttributeSourceAdvisor() {
        setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
    }

现在接口设置切点筛选条件:

 private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
            new Class[] {
                    RequiresPermissions.class, RequiresRoles.class,
                    RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
            };

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

最后由配置类:ShiroAnnotationProcessorConfiguration注入该切面

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

}
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值