1. 程式人生 > >shiro原始碼分析之自定義註解RequiredPermission(可代替RequiresPermissions)

shiro原始碼分析之自定義註解RequiredPermission(可代替RequiresPermissions)

1.現象

shiro使用RequiresPermissions等註解必須新增如下切面,否則不生效
@Bean
public AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    AuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new AuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}

2.原始碼分析

分析AuthorizationAttributeSourceAdvisor(繼承自StaticMethodMatcherPointcutAdvisor)原始碼發現,所有授權註解校驗都在AUTHZ_ANNOTATION_CLASSES中    
private static final Class<? extends Annotation>[] AUTHZ_ANNOTATION_CLASSES =
        new Class[] {
                RequiresPermissions.class, RequiresRoles.class,
                RequiresUser.class, RequiresGuest.class, RequiresAuthentication.class
        };
        
        
並且其構造方法中加了AopAllianceAnnotationsAuthorizingMethodInterceptor(繼承自AnnotationsAuthorizingMethodInterceptor)攔截器        
public AuthorizationAttributeSourceAdvisor() {
    setAdvice(new AopAllianceAnnotationsAuthorizingMethodInterceptor());
}   
    

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

      
分析註解對應攔截器(都繼承自AuthorizingAnnotationMethodInterceptor)發現,在其構造方法中新增校驗許可權的handler(PermissionAnnotationMethodInterceptor為例)
public PermissionAnnotationMethodInterceptor(AnnotationResolver resolver) {
    super( new PermissionAnnotationHandler(), resolver);
}


而他們的handler都是簡單的呼叫許可權校驗方法,原始碼如下
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]);
        
    }
}

3.自定義註解RequiredPermission, 用於授權校驗

這裡只是簡單的加了des, 可根據自己需求去改    
@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiredPermission {

    String[] value();

    Logical logical() default Logical.AND;

    String des() default "";
}

4.自定義MineAuthorizationAttributeSourceAdvisor繼承自StaticMethodMatcherPointcutAdvisor

其實和AuthorizationAttributeSourceAdvisor的區別是AUTHZ_ANNOTATION_CLASSES中加入自己的註解, 然後使用自己的攔截器MineAopAllianceAnnotationsAuthorizingMethodInterceptor
public class MineAuthorizationAttributeSourceAdvisor extends StaticMethodMatcherPointcutAdvisor {
    private static final Logger log = LoggerFactory.getLogger(org.apache.shiro.spring.security.interceptor.AuthorizationAttributeSourceAdvisor.class);

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

    protected SecurityManager securityManager = null;

    public MineAuthorizationAttributeSourceAdvisor() {
        setAdvice(new MineAopAllianceAnnotationsAuthorizingMethodInterceptor());
    }

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

}

5.自定義攔截器MineAopAllianceAnnotationsAuthorizingMethodInterceptor (繼承自AopAllianceAnnotationsAuthorizingMethodInterceptor)

就是把自己的RequiredPermissionAnnotationMethodInterceptor攔截器新增到advice中
public class MineAopAllianceAnnotationsAuthorizingMethodInterceptor extends AopAllianceAnnotationsAuthorizingMethodInterceptor {

    public MineAopAllianceAnnotationsAuthorizingMethodInterceptor() {
        super();
        this.methodInterceptors.add(new RequiredPermissionAnnotationMethodInterceptor(new SpringAnnotationResolver()));
    }
}

6.自定義RequiredPermissionAnnotationMethodInterceptor攔截器 (繼承自AuthorizingAnnotationMethodInterceptor)

定義自己的註解攔截器,並使用自己的RequiredPermissionAnnotationHandler
public class RequiredPermissionAnnotationMethodInterceptor extends AuthorizingAnnotationMethodInterceptor {

    public RequiredPermissionAnnotationMethodInterceptor(){
        super(new RequiredPermissionAnnotationHandler());
    }

    public RequiredPermissionAnnotationMethodInterceptor(AnnotationResolver resolver){
        super(new RequiredPermissionAnnotationHandler(), resolver);
    }
}

7.RequiredPermissionAnnotationHandler (繼承自AuthorizingAnnotationHandler)

在assertAuthorized中新增自己的校驗邏輯
public class RequiredPermissionAnnotationHandler extends AuthorizingAnnotationHandler {

    public RequiredPermissionAnnotationHandler() {
        super(RequiredPermission.class);
    }

    protected String[] getAnnotationValue(Annotation a) {
        RequiredPermission rpAnnotation = (RequiredPermission) a;
        return rpAnnotation.value();
    }

    @Override
    public void assertAuthorized(Annotation a) throws AuthorizationException {
        if (!(a instanceof RequiredPermission)) {
            return;
        }
        RequiredPermission rpAnnotation = (RequiredPermission) 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]);
            }
        }
    }
}

8.替換MineAuthorizationAttributeSourceAdvisor替換AuthorizationAttributeSourceAdvisor

@Bean
public MineAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor(SecurityManager securityManager) {
    MineAuthorizationAttributeSourceAdvisor authorizationAttributeSourceAdvisor = new MineAuthorizationAttributeSourceAdvisor();
    authorizationAttributeSourceAdvisor.setSecurityManager(securityManager);
    return authorizationAttributeSourceAdvisor;
}

9.測試

@RequestMapping(value="/addTeacher.do", method =RequestMethod.GET)
@RequiredPermission(value = {"teacher:add"}, des = "新增老師")
public ResultMessage addTeacher(){
   return ResultMessage.success("permission success.");
}

@RequestMapping(value="/updateTeacher.do", method =RequestMethod.GET)
@RequiredPermission(value = {"teacher:update"},  des = "新增老師")
public ResultMessage updateTeacher(){
   return ResultMessage.success("permission success.");
}

原始碼 https://gitee.com/jsjack_wang/springboot-demo dev-shiro分支