• 基于Spring Aop实现类似shiro的简单权限校验功能


    在我们的web开发过程中,经常需要用到功能权限校验,验证用户是否有某个角色或者权限,目前有很多框架,如Shiro

    Shiro有基于自定义登录界面的版本,也有基于CAS登录的版本,目前我们的系统是基于CAS单点登录,各个公司的单点登录机制略有差异,和Shiro CAS的标准单点登录校验方式也自然略有不同。

    在尝试将自定义登录的普通版Shiro改造失败后,在系统登录、校验角色、权限我认为相对简单后,觉得模仿Shiro自己实现一个权限校验小框架,说是框架,其实就是一个aop advisor,几个注解(Shiro不就是这个功能吗)

    RequiresRoles和RequiresPermissions相似,不截图了

    接下来是实现aop拦截功能了,先来说下注解的用法,和Shiro的注解一样,都是放在class或者method上

    上述配置的意思是需要角色user和admin,需要权限home:*,上面仅是一个例子,实际业务中的角色已经确定有哪些权限了。

     以下是aop拦截方式

    /**
     * 权限校验advisor,负责对RequiresRoles、RequiresPermissions标记的controller进行权限校验
     * 他执行的时机是interceptor之后、方法执行之前
     * Created by Administrator on 2015/2/9.
     */
    @Aspect
    @Component
    public class AuthExecuteAdvisor implements PointcutAdvisor, Pointcut {
    
        private static final Logger logger = LoggerFactory.getLogger(AuthExecuteAdvisor.class);
    
        @Autowired
        private UserService userService;
    
        /**
         * 生成一个始终匹配class的Filter对象,任何类都可以通过这个过滤器
         */
        private static final ClassFilter TrueClassFilter = new ClassFilter() {
            @Override
            public boolean matches(Class<?> clazz) {
                return true;
            }
        };
    
        /**
         * 生成根据特定注解进行过滤的方法匹配器
         * 如果class上有注解、或方法上有注解、或接口方法上有注解,都能通过
         */
        private MethodMatcher annotationMethodMatcher = new StaticMethodMatcher() {
            @Override
            public boolean matches(Method method, Class<?> targetClass) {
    
                if (targetClass.isAnnotationPresent(RequiresRoles.class) || targetClass.isAnnotationPresent(RequiresPermissions.class)) {
                    return true;
                }
    
                if (method.isAnnotationPresent(RequiresRoles.class) || method.isAnnotationPresent(RequiresPermissions.class)) {
                    return true;
                }
                // The method may be on an interface, so let's check on the target class as well.
                Method specificMethod = AopUtils.getMostSpecificMethod(method, targetClass);
    
    
                return (specificMethod != method && (specificMethod.isAnnotationPresent(RequiresRoles.class)
                        || specificMethod.isAnnotationPresent(RequiresPermissions.class)));
            }
    
        };
    
        @Override
        public ClassFilter getClassFilter() {
            //只执行注解的类
            return TrueClassFilter;
        }
    
        @Override
        public MethodMatcher getMethodMatcher() {
            return annotationMethodMatcher;
        }
    
        /**
         * 当前对象就是切面对象,根据classFilter、MethodFilter定义执行范围
         *
         * @return
         */
        @Override
        public Pointcut getPointcut() {
            return this;
        }
    
        /**
         * 切面对应的处理策略
         *
         * @return
         */
        @Override
        public Advice getAdvice() {
            //这个MethodInterceptor相当于MethodBeforeAdvice
            return new MethodInterceptor() {
                @Override
                public Object invoke(MethodInvocation invocation) throws Throwable {
    
                    LoginContext loginContext = ActionContext.getLoginContext();
                    if (loginContext == null) {
                        throw new AuthFailException("校验用户权限失败,用户未登录");
                    }
    
                    // TODO: 2017/11/21 管理员全权限
                    if (loginContext.getUserType().equals(0)) {
                        return invocation.proceed();
                    }
    
                    Set<String> alreadyRoles = new HashSet<>(userService.getRolesByUserId(loginContext.getUserId()));
                    Set<String> alreadyPermissions = new HashSet<>(userService.getPermissionByUserId(loginContext.getUserId()));
    
                    List<String> requireRole = getRequiredRole(invocation);
                    List<String> requirePermission = getRequiredPermission(invocation);
    
                    if (!CollectionUtils.isEmpty(requireRole) && !alreadyRoles.containsAll(requireRole)) {
                        //role权限不足
                        logger.error("校验用户权限失败,role角色不足");
    
                        throw new AuthFailException("校验用户权限失败,role角色不足");
                    }
    
                    if (!CollectionUtils.isEmpty(requirePermission) && !alreadyPermissions.containsAll(requirePermission)) {
                        //permission不足
                        logger.error("校验用户权限失败,permission权限不足");
                        throw new AuthFailException("校验用户权限失败,权限不足");
                    }
    
                    return invocation.proceed();
                }
    
    
            };
        }
    
        /**
         * 获取方法需要的角色
         *
         * @param invocation
         * @return
         */
        private List<String> getRequiredRole(MethodInvocation invocation) {
            List<String> requiredRoles = new ArrayList<>();
            Class<?> clazz = invocation.getThis().getClass();
    
            if (clazz.isAnnotationPresent(RequiresRoles.class)) {
                Collections.addAll(requiredRoles, clazz.getAnnotation(RequiresRoles.class).value());
            }
    
            if (invocation.getMethod().isAnnotationPresent(RequiresRoles.class)) {
                Collections.addAll(requiredRoles, invocation.getMethod().getAnnotation(RequiresRoles.class).value());
            }
    
            return requiredRoles;
        }
    
        /**
         * 获取方法需要的权限
         *
         * @param invocation
         * @return
         */
        private List<String> getRequiredPermission(MethodInvocation invocation) {
            List<String> requirePermission = new ArrayList<>();
            Class<?> clazz = invocation.getThis().getClass();
    
            if (clazz.isAnnotationPresent(RequiresPermissions.class)) {
                Collections.addAll(requirePermission, clazz.getAnnotation(RequiresPermissions.class).value());
            }
    
            if (invocation.getMethod().isAnnotationPresent(RequiresPermissions.class)) {
                Collections.addAll(requirePermission, invocation.getMethod().getAnnotation(RequiresPermissions.class).value());
            }
    
            return requirePermission;
        }
    
    
        /**
         * 每个切面的通知一个实例,或可分享的实例
         * 此处使用分享的实例即可,分享的实例在内存中只会创建一个Advice对象
         *
         * @return
         */
        @Override
        public boolean isPerInstance() {
            return false;
        }
    }
    

      

    此处主要有以下几个关键点:

        

    当前类既是一个切入点Pointcut(一群方法),也是一个通知持有者Advisor
    
    Pointcut接口通过ClassFilter、MethodMatcher锁定哪些方法会用到Advisor
    
    Advisor接口通过getAdvice获取Pointcut需要进行的拦截操作
    

     

    ClassFilter是一个始终返回True的类过滤器,因为我们拦截执行的最小单位是method,所以即使class上没有注解,也要让他通过拦截
    MethodMatcher则会判断method所在的class是否有注解,如果没有,则判断method是否有注解,二者满足其一就能通过校验
    
    Advice的具体拦截过程为:
    获取登录上下文
    获取当前method需要的权限和角色(这里实际可以再优化,因为method可能只需要Role或Permission其中一种权限)
    判断当前用户是否具有这些权限
    

      

     

    开启spring的aop功能,权限拦截开始生效

     
  • 相关阅读:
    05 redis中的Setbit位图法统计活跃用户
    04 redis list结构及命令详解
    03 redis之string类型命令解析
    02 redis通用命令操作
    Mesos提交任务没有被执行
    mesos的zookeeper变更
    VS Code使用git
    vs code 安装Scala
    打印正反读计算方式
    cloudera上面安装Spark2.0
  • 原文地址:https://www.cnblogs.com/windliu/p/8029666.html
Copyright © 2020-2023  润新知