前言:
  我看了下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, 实现了一个简易的支持复杂表达式权限验证的功能. 可能还不是特别完善, 但是对于小业务而言, 还是能够满足需求的, ^_^.

  

最新文章

  1. iOS组件化思路 &lt;转&gt;
  2. OpenGL显示图片
  3. 转:EntityFramework查询--联合查询(Join,GroupJoin)
  4. Spring MVC实现文件下载
  5. php面向对象之抽像类、接口、final、类常量
  6. Vim配置文件(Vimrc)
  7. SkinSoft中.vssf样式文件在VS2005中的应用(图文)
  8. mvc涉及到input设置了disabled
  9. MySQL存储过程详解 mysql 存储过程
  10. 关于IE7 兼容问题
  11. 如何彻底卸载sql server 2012
  12. ViEmu For VS2010 3.0 解除30天限制的方法
  13. Oracle Sql优化之分层查询(connect by)
  14. pyhton中的Queue(队列)
  15. python web开发之django
  16. Altium Designer 复制和粘贴功能
  17. lesson3-神经序列模型I-小象
  18. css实现两栏布局,左侧固定宽,右侧自适应的7中方法
  19. transport error 202: bind failed: 地址已在使用
  20. 06-老马jQuery教程-jQuery高级

热门文章

  1. Python3 批量更改文件后缀名
  2. Python while 循环
  3. 【题解】Luogu P2472 [SCOI2007]蜥蜴
  4. HTML5的自定义属性的使用总结
  5. flask --- 03 .特殊装饰器, CBV , redis ,三方组件
  6. freeswitch 获取当前网关通道数
  7. 浅谈加密算法BCrypt
  8. RN中API之NetInfo--浅谈
  9. 深入理解Plasma(三)Plasma MVP
  10. Andorid Studio中运行模拟器--夜神模拟器