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

本文介绍了如何扩展Apache Shiro的权限注解,创建自定义的@RequiresMethodPermissions。该注解旨在简化权限字符串规划,提高代码可读性。通过定义注解、重载Advisor和修改权限判断逻辑,实现根据类名和方法名动态生成权限字串。目前存在的三个挑战包括:自动将注解信息存入数据库、改进权限字符串存储的安全性和寻找更优雅的处理注解方式。

摘要生成于 C知道 ,由 DeepSeek-R1 满血版支持, 前往体验 >

今天想分享的是一个自定义的注解,@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对注解的处理,套了很多层,所以上面的修改其实并不美观,有没有更好的方法呢?
评论 1
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值