• 类Shiro权限校验框架的设计和实现


    前言:
      之前简单集成了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轻松实现.

  • 相关阅读:
    第10组 Beta冲刺(2/4)
    第10组 Beta冲刺(1/4)
    第10组 Alpha冲刺(4/4)
    第08组 Beta版本演示
    第08组 Beta冲刺(4/4)
    第08组 Beta冲刺(3/4)
    第08组 Beta冲刺(2/4)
    第08组 Beta冲刺(1/4)
    第08组 Alpha事后诸葛亮
    第08组 Alpha冲刺(4/4)
  • 原文地址:https://www.cnblogs.com/mumuxinfei/p/9339086.html
Copyright © 2020-2023  润新知