利用AOP实现简单的权限管理

407 阅读2分钟

文章概览

  1. 权限管理
  2. 代码实现
  3. 用法用例

权限管理

  1. 什么是权限
  • 用大白话举例子,校长可以审批老师的请假,但是主任还不够格无法操作,所以说校长具备审批请假的权限
  • 按上面的例子,那么审批请假应该定义为一个资源,那么我用一串字符串来定义就是 audit_leave:teacher:rw.相同地,老师可以审批学生请假,那么就是audit_leave:student:rw
  1. 如何使用权限管理
  • 权限管理的粒度可以切分到很小,可以是整个功能,可以是某个接口
  • 由于题主是搞Java的,在Java社区,关于权限框架比较耳熟能详的就是Shiro,Spring Security

如何实现一个简单的接口权限管理

  1. 关键字 [简单/权限]
  2. 如何实现
  • 自定义注解,利用AOP来做接口拦截
  1. 代码实现如下:
  • 定义一个用于接口方法的注解,表明该方法需要权限校验
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.METHOD})
public @interface RequirePermission {
    String[] value() default "";
}
  • 额外定义2个注解可以选择性辅助上述注解的使用
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER,ElementType.FIELD})
public @interface PermissionParam {
    String target() default "";
}
@Retention(RetentionPolicy.RUNTIME)
@Target({ElementType.PARAMETER})
public @interface PermissionParamObj {
}
  • 切面实现
@Slf4j
@Aspect
@Component
public class PermissionProcessor {

    @Before("@annotation(org.example.app.annotation.RequirePermission)")
    public void checkPermission(JoinPoint joinpoint) throws Exception {
        MethodSignature signature = (MethodSignature) joinpoint.getSignature();
        Method method = signature.getMethod();
        RequirePermission annotation = method.getAnnotation(RequirePermission.class);
        if (annotation == null) {
            return;
        }
        // 所需要的权限资源
        String[] value = annotation.value();
        if (value == null || value.length == 0) {
            return;
        }
        Object[] args = joinpoint.getArgs();
        if (args == null || args.length == 0) {
            return;
        }
        // 填充占位符
        fixExpression(value, signature.getMethod(), args);
        // 查找当前发现操作人的权限资源集合
        Set<String> curPermissions = findCurPermissions();
        // 做比较
        if (checkPermission(value, curPermissions)) {
            return;
        }
        // TODO 抛个异常
    }

    // 填充占位符 #{target}
    private void fixExpression(String[] requires, Method method, Object[] args) {
        // RequirePermission
        Map<String, String> replaceResult = null;
        int arrayLength = requires.length;
        for (int i = 0; i < arrayLength; i++) {
            String expression = requires[i];
            String[] strArray = StringUtils.split(expression, PermissionExpression.SPLIT);
            if (strArray.length != 3) {
                log.error("duck find the error permission param[length not equals 3]");
                continue;
            }
            String resourceId = strArray[1];
            if (!StringUtils.startsWith(resourceId, "#{") || !StringUtils.endsWith(resourceId, "}")) {
                continue;
            }
            String key = StringUtils.substring(resourceId, 2, resourceId.length() - 1);
            if (Objects.isNull(replaceResult)) {
                replaceResult = initPermissionParamMap(method, args);
            }
            String value = replaceResult.get(key);
            if (StringUtils.isBlank(value)) {
                continue;
            }
            // 替换
            requires[i] = strArray[0] + PermissionExpression.SPLIT + value + PermissionExpression.SPLIT + strArray[2];
        }
    }


    private Map<String, String> initPermissionParamMap(Method method, Object[] args) {
        Map<String, String> map = null;
        for (int i = 0; i < args.length; i++) {
            MethodParameter methodParameter = new MethodParameter(method, i);
            PermissionParam permissionParam = methodParameter.getParameterAnnotation(PermissionParam.class);
            if (permissionParam == null) {
                if (methodParameter.getParameterAnnotation(PermissionParamObj.class) == null) {
                    continue;
                }
            }
            // 初始化
            if (map == null) {
                map = new HashedMap();
            }
            if (permissionParam != null) {
                map.put(permissionParam.target(), String.valueOf(args[i]));
            } else {
                // 反射遍历对象
                Object arg = args[i];
                findFieldAndParse(arg, map);
            }
        }
        return map;
    }

    private void findFieldAndParse(Object arg, Map<String, String> map) {
        Class<?> aClass = arg.getClass();
        while (aClass != null && !aClass.equals(Object.class)) {
            Field[] fields = aClass.getDeclaredFields();
            if (fields != null && fields.length > 0) {
                for (Field field : fields) {
                    PermissionParam permissionParam = field.getAnnotation(PermissionParam.class);
                    if (permissionParam == null) {
                        continue;
                    }
                    field.setAccessible(true);
                    try {
                        map.put(permissionParam.target(), String.valueOf(field.get(arg)));
                    } catch (Exception e) {
                        log.error("duck permission check error[reflection error]", e);
                    }
                }
            }
            aClass = aClass.getSuperclass();
        }
    }

    // 比对权限资源
    private boolean checkPermission(String[] value, Set<String> curPermissions) {
        for (String resource : value) {
            if (!curPermissions.contains(resource)) {
                return false;
            }
        }
        return true;
    }

    // TODO 数据库查询
    private Set<String> findCurPermissions() {
        return null;
    }
}

结尾

  1. 一个简单的例子实现基于权限描述符的权限管理,实现依赖 [spring boot/spring aop/mysql]
  2. 感谢阅读,有什么问题或者文中有什么错误可以留言告诉我
  3. 今后会不断地更新掘金,当作一个技术博客来写,后续会把一些源码分析,学习思路等分享上来,欢迎关注,我也会持续优化自己的表达能力,写出更好更容易理解的文章