类Shiro权限校验框架的设计和实现(2)--对复杂权限表达式的支持
前言:
我看了下shiro好像默认不支持复杂表达式的权限校验, 它需要开发者自己去做些功能扩展的工作. 针对这个问题, 同时也会为了弥补上一篇文章提到的支持复杂表示需求, 特地尝试写一下解决方法.
本文主要借助groovy脚本来实现复杂表达式的计算, 其思想是借鉴了Oval支持复杂表达式(groovy/javascript/ruby)的实现方式.
文章系列:
1. springmvc简单集成shiro
2. 类Shiro权限校验框架的设计和实现
3. 权限系统(RBAC)的数据模型设计
目标设定:
引入注解@MyEvaluateExpression, 其支持Groovy语法的布尔表达式(&&, ||, !), 用于复杂的权限计算评估.
表达式内部定义了两个函数:
1 2 3 4 | // *) 判断是否拥有该角色 boolean hasRole(String role); // *) 判断是否拥有该权限 boolean hasPermission(String permission); |
举例如下:
case 1:
1 | @MyEvaluateExpression (expr= "hasRole('developer') || hasPermission('blog:write')" ) |
表示了要么角色为developer, 要么该 用户有blog的写权限, 这个接口数据就能访问.
case 2:
1 | @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实现代码热载的机制和原理.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 | 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:
1 2 3 4 5 | @Target ({ElementType.TYPE, ElementType.METHOD}) @Retention (RetentionPolicy.RUNTIME) public @interface MyEvaluateExpression { String expr(); } |
扩展MyShiroHelper类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 | 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类
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 | @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类上添加方法.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 | @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, 实现了一个简易的支持复杂表达式权限验证的功能. 可能还不是特别完善, 但是对于小业务而言, 还是能够满足需求的, ^_^.
posted on 2018-07-23 23:48 mumuxinfei 阅读(1078) 评论(0) 编辑 收藏 举报
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 基于Microsoft.Extensions.AI核心库实现RAG应用
· Linux系列:如何用heaptrack跟踪.NET程序的非托管内存泄露
· TypeScript + Deepseek 打造卜卦网站:技术与玄学的结合
· 阿里巴巴 QwQ-32B真的超越了 DeepSeek R-1吗?
· 【译】Visual Studio 中新的强大生产力特性
· 10年+ .NET Coder 心语 ── 封装的思维:从隐藏、稳定开始理解其本质意义
· 【设计模式】告别冗长if-else语句:使用策略模式优化代码结构