前言:
之前简单集成了springmvc和shiro用于后台管理平台的权限控制, 设计思路非常的优美, 而且编程确实非常的方便和简洁. 唯一的不足, 我觉得配置稍有些繁琐. 当时我有个小想法, 觉得可以写个更小巧版的shiro, 用于权限控制.
因为shiro本身不涉及权限的数据模型, 而且权限控制这块也只是它的一小部分功能点. 因此剥离后的工作量, 预计不是很大.
相关文章列表:
1. springmvc简单集成shiro
2. 利用Aspectj实现Oval的自动参数校验
设计目标
首先来讲讲定位吧, Shiro本身是一个安全组件, 可以复用. 但是我这个版本, 倾向于半成品. 它属于一种设计思路, 多少和业务有些耦合, 它有一定的借鉴作用, 其他简易项目可以拿去copy/paste, 然后修改下代码, 就能使用上的, ^_^.
列一下设计目标吧:
1. 只支持权限校验, 不涉及认证
2. 支持role/permission的注解校验(包括简单的与或操作)
就这么简单了, ^_^.
实践:
整个小框架, 是基于springmvc环境下, 其数据存储于http session中, 但除此以外, 和外界再无瓜葛, ^_^.
它的核心就一个工具类, 一个切面类, 两个注解, 非常的简洁.
权限注解类的引入:
// *) 逻辑与或 public enum MyLogic { AND, OR; } // *) 定义权限校验注解 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequiresPermissions { String[] value(); MyLogic logic() default MyLogic.AND; } // *) 定义角色校验注解 @Target({ElementType.TYPE, ElementType.METHOD}) @Retention(RetentionPolicy.RUNTIME) public @interface MyRequiresRoles { String[] value(); MyLogic logic() default MyLogic.AND; }
工具类引入:
@Getter public class MyAuthorizeInfo { private Set<String> roles = new TreeSet<String>(); private Set<String> permissions = new TreeSet<String>(); public void addRole(String role) { roles.add(role); } public void addPermission(String permission) { permissions.add(permission); } } public class MyShiroHelper { private static final String MY_SHIRO_AUTHRIZE_KEY = "my_shiro_authorize_key"; // 授权权限列表 public static void authorize(MyAuthorizeInfo authorizeInfo) { HttpSession session = getSession(); session.setAttribute(MY_SHIRO_AUTHRIZE_KEY, authorizeInfo); } // 验证角色 public static boolean validateRoles(String[] roles, MyLogic logic) throws Exception { if ( roles == null || roles.length == 0 ) { return true; } try { HttpSession session = getSession(); MyAuthorizeInfo authorizeInfo = (MyAuthorizeInfo)session .getAttribute(MY_SHIRO_AUTHRIZE_KEY); if ( authorizeInfo == null ) { return false; } return evaluateExpression(roles, logic, authorizeInfo.getRoles()); } catch (Exception e) { throw new Exception("permission invalid state"); } finally { } } // 验证权限 public static boolean validatePermssion(String[] permissions, MyLogic logic) throws Exception { if ( permissions == null || permissions.length == 0 ) { return true; } try { HttpSession session = getSession(); MyAuthorizeInfo authorizeInfo = (MyAuthorizeInfo)session .getAttribute(MY_SHIRO_AUTHRIZE_KEY); if ( authorizeInfo == null ) { return false; } return evaluateExpression(permissions, logic, authorizeInfo.getPermissions()); } catch (Exception e) { throw new Exception("permission invalid state"); } finally { } } // *) 与或操作, 布尔表达式的计算 public static boolean evaluateExpression(String[] values, MyLogic logic, Set<String> sets) { if ( MyLogic.AND == logic ) { for ( String val : values ) { if ( !sets.contains(val) ) { return false; } } return true; } else if ( MyLogic.OR == logic ) { for ( String val : values ) { if ( sets.contains(val) ) { return true; } } return false; } return true; } // *) 获取Http Session private static HttpSession getSession() { HttpServletRequest request = ((ServletRequestAttributes) RequestContextHolder.getRequestAttributes()) .getRequest(); return request.getSession(); } }
最后是切面类的引入:
@Aspect @Component public class MyShiroAdvice { /** * 定义切点, 用于权限的校验 */ @Pointcut("@annotation(com.springapp.mvc.myshiro.MyRequiresPermissions)") public void checkPermissions() { } /** * 定义切点, 用于角色的校验 */ @Pointcut("@annotation(com.springapp.mvc.myshiro.MyRequiresRoles)") public void checkRoles() { } @Before("checkPermissions()") public void doCheckPermission(JoinPoint jp) throws Exception { // *) 获取对应的注解 MyRequiresPermissions mrp = extractAnnotation( (MethodInvocationProceedingJoinPoint)jp, MyRequiresPermissions.class ); try { if ( !MyShiroHelper.validatePermssion(mrp.value(), mrp.logic()) ) { throw new Exception("access disallowed"); } } catch (Exception e) { throw new Exception("invalid state"); } } @Before("checkRoles()") public void doCheckRole(JoinPoint jp) throws Exception { // *) 获取对应的注解 MyRequiresRoles mrp = extractAnnotation( (MethodInvocationProceedingJoinPoint)jp, MyRequiresRoles.class ); try { if ( !MyShiroHelper.validateRoles(mrp.value(), mrp.logic()) ) { 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); } }
这边为了简洁, 对异常类没有做细分处理.
实战:
那如何集成, 并使用上述的小shiro框架呢?
首先激活Aspectj, 同时把注解类纳入到spring容器中.
<aop:aspectj-autoproxy proxy-target-class="true"/> <context:component-scan base-package="com.xxx.myshiro"/> <!-- 切面类所在的package -->
而在具体的实战中, 可以写个简单的测试RestController类, 来演示下整个流程.
@Controller @RequestMapping("/") public class HelloController { @RequestMapping(value="/login", method={RequestMethod.POST, RequestMethod.GET}) @ResponseBody 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 = "/test1", method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody @MyRequiresPermissions(value={"blog:write", "blog:update"}, logic=MyLogic.AND) public String test1() { return "test1"; } @RequestMapping(value = "/test2", method = {RequestMethod.GET, RequestMethod.POST}) @ResponseBody @MyRequiresPermissions(value={"blog:write", "blog:update"}, logic=MyLogic.OR) public String test2() { return "test2"; } }
注: 关注下login接口, 这里完成了登陆和授权工作.
这个小框架, 把用户登陆和权限数据模型的设计获取交给了业务开发者, 只保留了权限校验.
测试:
在完成登陆login之后
1. 调用/test1, 访问受限(需要blog:write && blog:update, 但是只有blog:write权限)
2. 调用/test2, 访问通过(需要blog:write || blog:update, 有blog:write权限)
总结:
在抛离认证和账号管理之后, 其实这个权限校验小框架, 其实非常的漂亮和实用. 真正在业务开发时, 遇到小的项目, 其实可以直接使用, 而并非要选择较重的shiro.
当然在权限校验这块, 都是针对静态资源的权限校验, 若遇到动态权限校验(比如用户对自己编辑的文章有编辑权限, 其他用户没), 还是有所不足的.
不过, 如果有复杂的权限表达式计算需求的, 这个不算什么难点, 可以借助Groovy轻松实现.