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

        try {
            if ( !MyShiroHelper.validateExpression(mrp.expr()) ) {
                throw new Exception("access disallowed");
            }
        } catch (Exception e) {
            throw new Exception("invalid state");
        }

    }

    // *) 获取注解信息
    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, 实现了一个简易的支持复杂表达式权限验证的功能. 可能还不是特别完善, 但是对于小业务而言, 还是能够满足需求的, ^_^.

  

原文地址:https://www.cnblogs.com/mumuxinfei/p/9355843.html

时间: 2024-08-05 06:25:11

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

自己写的基于java Annotation(注解)的数据校验框架

JavaEE6中提供了基于java Annotation(注解)的Bean校验框架,Hibernate也有类似的基于Annotation的数据校验功能,我在工作中,产品也经常需要使 用数据校验,为了方便和重用,自己写了一个简单的基于Annotation的校验框架.有兴趣的可以扩展. 框架说明: AnnotationValidable接口:所有需要使用该校验框架的类都要实现它,该类中没有任何方法需要实现,仅仅是一个表明那些类需要使用该校验框架的标识. GetFiledValue类:是一个工具类,对

Shiro + SSM(框架) + Freemarker(jsp)

Shiro + SSM(框架) + Freemarker(jsp)讲解的权限控制Demo,还不赶快去下载? 我们知道Ajax不能做页面redirect和forward跳转,所以Ajax请求假如没登录,那么这个请求给用户的感觉就是没有任何反应,而用户又不知道用户已经退出或是  Session  超时了.这个时候如何解决? Shiro 教程,Ajax请求拦截跳转页面方案 在登录拦截器中,如我们自己定义的LoginFilter 中,先行判断下,是否为  Ajax  请求,如果是  Ajax  请求并且

java企业级通用权限安全框架源码 SpringMVC mybatis or hibernate+ehcache shiro druid bootstrap HTML5

开发快报: 页面打印功能,websocket 强制下线功能,玩转websocket技术  [金牌] 获取[下载地址]   QQ: 313596790 A 代码生成器(开发利器);      增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成    就不用写搬砖的代码了,生成的放到项目里,可以直接运行 B 阿里巴巴数据库连接池druid;   数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性

java企业级通用权限安全框架源码 SpringMVC mybatis or hibernate+ehcache shiro druid bootstrap HTML

开发快报: 页面打印功能,websocket 强制下线功能,玩转websocket技术  [金牌]获取[下载地址]   QQ: 313596790A 代码生成器(开发利器);     增删改查的处理类,service层,mybatis的xml,SQL( mysql   和oracle)脚本,   jsp页面 都生成   就不用写搬砖的代码了,生成的放到项目里,可以直接运行B 阿里巴巴数据库连接池druid;  数据库连接池  阿里巴巴的 druid.Druid在监控.可扩展性.稳定性和性能方面都

Shiro实现用户对动态资源细粒度的权限校验

前言 在实际系统应用中,普遍存在这样的一种业务场景,需要实现用户对要访问的资源进行动态权限校验. 譬如,在某平台的商家系统中,存在商家.品牌.商品等业务资源.它们之间的关系为:一个商家可以拥有多个品牌,一个品牌下可以拥有多个商品. 一个商家用户可以拥有多个账户,每个账户拥有不同级别的权限. 例如,小王负责商家A下的所有资源的运营工作,小张负责品牌A和品牌A下所有商品的运营工作.而小李负责品牌B Shiro本身提供了RequiresAuthentication.RequiresPermission

Shiro权限控制框架入门1:Shiro的认证流程以及基本概念介绍

前言:我在最开始学习Shiro这个框架时,在网上搜索到的一个介绍比较全面的教程是:<跟我学Shiro>系列教程.但是在我看了他写的前几篇文章后,我发现虽然他在这个系列教程中把shiro的一些特性介绍地非常全面详细,但是整个教程的叙述方式还是有很大缺陷的.文章与文章之间并没有很好地串联起来,每篇文章介绍的东西都过于分散了,如果是对shiro完全不了解的新手来看的话完全是一场噩梦.就像一个网友评价的这样: 看了看这个教程,看完之后都想放弃shiro了,完全看不懂,后来百度了很多别的资料才理解了sh

通用权限系统框架功能实现设计

1  开发环境技术:B/S(.NET C# ) 1.Windows 7及以上 (支援最新Win 8) 2.Microsoft Visual Studio 2013 C#.NET 3..NET Framework 4.0及以上 (支援最新4.5版本) 4.SQL Server 2008 R2及以上 (支援2012/2014)框架特点 2  系统简介 1.帮企业快速地实现各种通用功能,结合系统现有的通用权限管理功能. 2.快速地开发出各种项目应用系统.让企业开发一个系统变得非常轻松. 3.符合RBA

erp权限验证框架Shiro

  权限验证框架Shiro Shiro简介 1.1. 什么是Shiro Apache Shiro是一个强大易用的Java安全框架,提供了认证.授权.加密和会话管理等功能: 认证(Authentication):用户身份识别,常被称为用户"登录",判断用户是否登陆,如果未登陆,则拦截其请求 授权(Authorization):访问控制.当用户登陆后,判断其身份是否有权限访问相应的资源,如果没有权限则拦截 密码加密(Cryptography):保护或隐藏数据防止被偷窃.将MD5进行二次封装

Apache shiro之权限校验流程

从张开涛blog学习后整理:http://jinnianshilongnian.iteye.com/blog/2018398 图片原图比较大,建议将图片在新的选项卡打开后100%大小浏览 在权限校验中RolePermissionResolver接口用于将角色所包含的权限全部解析出来(生成Permission实例)