• 类Shiro权限校验框架的设计和实现(2)--对复杂权限表达式的支持


    前言:
      我看了下shiro好像默认不支持复杂表达式的权限校验, 它需要开发者自己去做些功能扩展的工作. 针对这个问题, 同时也会为了弥补上一篇文章提到的支持复杂表示需求, 特地尝试写一下解决方法.
      本文主要借助groovy脚本来实现复杂表达式的计算, 其思想是借鉴了Oval支持复杂表达式(groovy/javascript/ruby)的实现方式.

    文章系列:
      1. springmvc简单集成shiro 
      2. 类Shiro权限校验框架的设计和实现 
      3. 权限系统(RBAC)的数据模型设计 

    目标设定:
      引入注解@MyEvaluateExpression, 其支持Groovy语法的布尔表达式(&&, ||, !), 用于复杂的权限计算评估.
      表达式内部定义了两个函数:

    // *) 判断是否拥有该角色
    boolean hasRole(String role);
    // *) 判断是否拥有该权限	
    boolean hasPermission(String permission); 

      举例如下:
      case 1:

    @MyEvaluateExpression(expr="hasRole('developer') || hasPermission('blog:write')")

      表示了要么角色为developer, 要么该 用户有blog的写权限, 这个接口数据就能访问.
      case 2:

    @MyEvaluateExpression(expr="!hasRole('developer') && hasPermission('blog:write')")

      表示了要么角色不能是developer, 同时该用户要有blog的写权限, 这个接口数据才能访问.

    实现:
      目标设定了, 选型也明确了, 那一切就好办了, ^_^.
      对自定义函数hasRole, hasPermission, 其实是个伪函数, 这个表达式在使用groovy执行引擎执行前, 会被代码替换.
      比如表达式: hasRole('developer') || hasPermission('blog:write')
      会被替换为: rolesSet.contains('developer') || permissionSet.contains('blog:write')
      其中rolesSet/permissionSet分别是角色/权限集合的set变量, 它们是外部注入脚本的bind变量.
      然后在groovy引擎中执行时, 就很容易了.

    完整代码:
      参考先前的一篇文章: Groovy实现代码热载的机制和原理.

    public class MyShiroGroovyHelper {
    
        private static ConcurrentHashMap<String, Class<Script>> zlassMaps
                = new ConcurrentHashMap<String, Class<Script>>();
    
        // *) 具体执行groovy代码
        public static Object invoke(String scriptText, Map<String, Object> params) {
            String key = fingerKey(scriptText);
            Class<Script> script = zlassMaps.get(key);
            if ( script == null ) {
                synchronized (key.intern()) {
                    // Double Check
                    script = zlassMaps.get(key);
                    if ( script == null ) {
                        GroovyClassLoader classLoader = new GroovyClassLoader();
                        script = classLoader.parseClass(scriptText);
                        zlassMaps.put(key, script);
                    }
                }
            }
    
            Binding binding = new Binding();
            for ( Map.Entry<String, Object> ent : params.entrySet() ) {
                binding.setVariable(ent.getKey(), ent.getValue());
            }
            Script scriptObj = InvokerHelper.createScript(script, binding);
            return scriptObj.run();
    
        }
    
        // *) 为脚本代码生成md5指纹
        private static String fingerKey(String scriptText) {
            try {
                MessageDigest md = MessageDigest.getInstance("MD5");
                byte[] bytes = md.digest(scriptText.getBytes("utf-8"));
    
                final char[] HEX_DIGITS = "0123456789ABCDEF".toCharArray();
                StringBuilder ret = new StringBuilder(bytes.length * 2);
                for (int i=0; i<bytes.length; i++) {
                    ret.append(HEX_DIGITS[(bytes[i] >> 4) & 0x0f]);
                    ret.append(HEX_DIGITS[bytes[i] & 0x0f]);
                }
                return ret.toString();
            } catch (Exception e) {
                throw new RuntimeException(e);
            }
        }
    
    }

      在类Shiro权限校验框架的设计和实现, 继续做扩充
      定义注解@MyEvaluateExpression:

    @Target({ElementType.TYPE, ElementType.METHOD})
    @Retention(RetentionPolicy.RUNTIME)
    public @interface MyEvaluateExpression {
        String expr();
    }

      扩展MyShiroHelper类

    public class MyShiroHelper {
    
        private static final String MY_SHIRO_AUTHRIZE_KEY = "my_shiro_authorize_key";
    
    	// *) 评估复杂表达式
        public static boolean validateExpression(String expr) throws Exception {
            if ( expr == null ) {
                throw new Exception("invalid expression");
            }
            try {
                HttpSession session = getSession();
                MyAuthorizeInfo authorizeInfo = (MyAuthorizeInfo)session
                        .getAttribute(MY_SHIRO_AUTHRIZE_KEY);
                if ( authorizeInfo == null ) {
                    return false;
                }
                String scriptText = expr.replaceAll("hasRole", "roleSet.contains");
                scriptText = scriptText.replaceAll("hasPermission", "permissionSet.contains");
    
                Map<String, Object> params = new TreeMap<String, Object>();
                params.put("roleSet", authorizeInfo.getRoles());
                params.put("permissionSet", authorizeInfo.getPermissions());
                return (Boolean) MyShiroGroovyHelper.invoke(scriptText, params);
            } catch (Exception e) {
                throw new Exception("permission invalid state");
            } finally {
            }
        }
    
    	private static HttpSession getSession() {
            HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes())
                    .getRequest();
            return request.getSession();
        }
    
    }

      扩展MyShiroAdvice类

    @Aspect
    @Component
    public class MyShiroAdvice {
    
        /**
         * 定义切点
         */
        @Pointcut("@annotation(com.springapp.mvc.myshiro.MyEvaluateExpression)")
        public void checkExprs() {
        }
    
        @Before("checkExprs()")
        public void doCheckExprs(JoinPoint jp) throws Exception {
    
            // *) 获取对应的注解
            MyEvaluateExpression mrp = extractAnnotation(
                    (MethodInvocationProceedingJoinPoint)jp,
                    MyEvaluateExpression.class
            );
    
            boolean res = true;
            try {
                res = MyShiroHelper.validateExpression(mrp.expr());
            } catch (Exception e) {
                throw new Exception("invalid state");
            }
            if ( !res ) {
                throw new Exception("access disallowed");
            }
    
        }
    
        // *) 获取注解信息
        private static <T extends Annotation> T extractAnnotation(
                MethodInvocationProceedingJoinPoint mp, Class<T> clazz) throws Exception {
    
            Field proxy = mp.getClass().getDeclaredField("methodInvocation");
            proxy.setAccessible(true);
    
            ReflectiveMethodInvocation rmi = (ReflectiveMethodInvocation) proxy.get(mp);
            Method method = rmi.getMethod();
    
            return (T) method.getAnnotation(clazz);
        }
    
    }
    

      

    测试代码:
      在之前的Controller类上添加方法.

    @RestController
    @RequestMapping("/")
    public class HelloController {
    
    	@RequestMapping(value="/login", method={RequestMethod.POST, RequestMethod.GET})
    	public String login() {
    		// 1) 完成登陆验证
    		// TODO
    
    		// 2) 查询权限信息
    		// TODO
    
    		// 3) 注册权限信息
    		MyAuthorizeInfo authorizeInfo = new MyAuthorizeInfo();
    		authorizeInfo.addRole("admin");
    
    		authorizeInfo.addPermission("blog:write");
    		authorizeInfo.addPermission("blog:read");
    
    		// *) 授权
    		MyShiroHelper.authorize(authorizeInfo);
    		return "ok";
    	}
    
    	@RequestMapping(value="/test3", method={RequestMethod.GET, RequestMethod.POST})
    	@MyEvaluateExpression(expr="hasRole('admin') && !hasPermission('blog:write')")
    	public String test3() {
    		return "test3";
    	}
    
    }
    

      测试符合预期.

    总结:
      本文利用了Aspectj和Groovy, 实现了一个简易的支持复杂表达式权限验证的功能. 可能还不是特别完善, 但是对于小业务而言, 还是能够满足需求的, ^_^.

      

      

  • 相关阅读:
    设计模式
    mysql引擎与物理文件
    org.mybatis.spring.MyBatisSystemException: nested exception is org.apache.ibatis.reflection.ReflectionException: There is no getter for property named 'socialCode' in 'class java.lang.String'
    bad SQL grammar []; nested exception is com.mysql.jdbc.exceptions.jdbc4.MySQLSyntaxErrorException
    jedis异常Broken pipe (Write failed)
    java第三方工具包
    mysql安装(centos7)
    sftp安装(linux)
    Snmp oid对应信息
    RAID
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/9355843.html
Copyright © 2020-2023  润新知