Shiro介绍(六):扩展自己的@RequiresPermission

时间:2022-09-24 05:28:01

今天想分享的是一个自定义的注解,@RequiresMethodPermissions,为什么想到要自定义这个注解?因为Shiro为我们提供的关于权限的注解@RequiresPermissions需要一个Permission的参数。

实践中,需要我们事先对所有权限字串(假如使用通配符Permission)做出一个规划,然后所有Coder在编码过程中严格按这个权限表来coding。

于是,我们可以偷懒一下,将权限字串规划这样定义,就使用完整的类名加方法名,即: [ClassName]:[MethodName],那么我们的注解就变成了这样的模样:

@RequiresPermissions("cn.sharetop.example.HelloController:addUser")

那么,问题来了,能不能更简化一下,并且增加一点可读性,比如这样呢:

@RequiresPermissions("新增用户")

所以,我们需要自定义这个注解了,且叫做 @RequiresMethodPermissions 吧。

第一步,定义注解。这里的value是作为权限的中文描述,并非用来判断的权限字串,真正用来判断的字串其实是被注解的方法及所在的类名:

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
public @interface RequiresMethodPermissions {
String value() default "";
}

第二步,重载Shiro的Advisor。

/**
* 实现全套Permission的注解处理
*
* @author yancheng
*
* 逻辑:
*
* 从advisor 将当前注解的class/method,拼出className:methodName,作为权限字串
* 将自已advisor作为引用传给authzMethodInterceptor
* 再透传给MethodPermission的MethodInterceptor
* 再透传到MethodPermission的AnnotationHandler
* 在Handler的判断方法assertAuthorized中,校验的依据不是来自Annotation中的value而是这个透传来的advisor.permissionString
*
* */

@SuppressWarnings({"rawtypes"})
public class AuthzAttributeSourceAdvisor extends AuthorizationAttributeSourceAdvisor {
private static final long serialVersionUID = -2886148353542507772L;

protected String permissionString;
public String getPermissionString() {
return permissionString;
}
public void setPermissionString(String permissionString) {
this.permissionString = permissionString;
}

private boolean _checkMethodPermissions(Method m,Class targetClass){
Annotation a = AnnotationUtils.findAnnotation(m,RequiresMethodPermissions.class);
if(a!=null){

StringBuilder sb = new StringBuilder();
sb.append(targetClass.getName());
sb.append(":");
sb.append(m.getName());

setPermissionString(sb.toString());

return true;
}
return false;
}
public boolean matches(Method method, Class targetClass) {

if( _checkMethodPermissions(method,targetClass) )
return true;

return super.matches(method, targetClass);
}

public AuthzAttributeSourceAdvisor() {
setAdvice(new AopAnnotationAuthzMethodInterceptor(this));
}
}

从代码可以看出来,在这个advisor中,我们用类名与方法名拼装出一个真正用于判断权限的字串,并保存在自己身上(注:这只是示例代码,真实使用中需要考虑一下线程安全性)。然后就是传递了……

第三步:传递自己,上一段代码中,构造函数中,将自己(advisor)传给了AopAnnotationAuthzMethodInterceptor,并且后者将这个引用又透传到一个专门处理此注解的方法拦截器MethodPermissionAnnotationMethodInterceptor,最终的被透传到真正处理我们这个注解的Handler中。

public class MethodPermissionAnnotationHandler extends AuthorizingAnnotationHandler {

protected AuthzAttributeSourceAdvisor advisor;

public MethodPermissionAnnotationHandler(){
super(RequiresMethodPermissions.class);
this.advisor=new AuthzAttributeSourceAdvisor();
}

public MethodPermissionAnnotationHandler(AuthzAttributeSourceAdvisor advisor) {
super(RequiresMethodPermissions.class);
this.advisor=advisor;

}

@Override
public void assertAuthorized(Annotation a) throws AuthorizationException {
// TODO Auto-generated method stub
RequiresMethodPermissions rpAnnotation = (RequiresMethodPermissions)a;
String value = rpAnnotation.value();
Subject subject = getSubject();

String permissionString=this.advisor.getPermissionString();
if(!StringUtils.isEmpty(permissionString)){
subject.checkPermission(permissionString);
return;
}

}

}

在Handler中,我们改写权限的判断,不能使用注解带来的value,必须使用advisor中的permissionString来做判断确定是否拥有权限。

至此代码就完工了,可以尝试一下。

@RequiresMethodPermissions("进入管理台")
@RequestMapping(value="/admin"
,method=RequestMethod.GET
,headers = {"Accept=text/html"})
public ModelAndView showAdmin(){
ModelAndView mv = new ModelAndView();

String cnt = RandomUtils.nextLong()+"";
mv.addObject("message", cnt);

mv.setViewName("admin");
return mv;
}

证明是OK的。

现在有三个问题:

  1. 更偷懒的是,我们还需要一个能自动将被注解的Controller的Method以及对应的字串加入数据库,以方便我们通过后台配置。我觉得,可以使用Maven的插件,在package阶段来做这件事,随后再尝试。
  2. 在advisor中保存这么一个字串,其实不安全,因为spring缺省情况下,装配出的advisor是单体。我觉得应该放在一个Map中会好一些吧。
  3. 因为Shiro对注解的处理,套了很多层,所以上面的修改其实并不美观,有没有更好的方法呢?