文章目录

  • 一、接口鉴权方案分析
  • 1、接口鉴权方案
  • 2、角色分配权限树
  • 二、编码实战
  • 1、定义权限树与常用方法
  • 2、自定义AOP注解
  • 3、AOP切面类(也可以用拦截器实现)
  • 4、测试一下


一、接口鉴权方案分析

1、接口鉴权方案

目前大部分接口鉴权方案,一般都是采用 【用户-角色-权限】模型。

将接口与权限进行编码死绑定,同时角色与权限进行动态绑定,用户与角色也进行动态绑定。

2、角色分配权限树

创建用户之后,用户可以分配多个角色。

创建角色之后,通过查询代码内置的权限树,进行角色与权限绑定,存入数据库中。

基于SpringAOP实现自定义接口权限控制_普通用户

二、编码实战

1、定义权限树与常用方法

使用枚举进行权限的定义,通过四级权限树,将权限分为模块、单元、功能级、接口。接口权限精细分配:

/**
 * 接口权限枚举定义类
 */
public enum AuthPermission {
    /**
     * 四级权限树
     * 1 模块
     * - 2 功能
     * - - 3 接口集(一般是Controller)
     * - - - 4 具体接口(@RequestMapping)
     *
     */

    // 用户管理
    User("user", "用户", Type.Module),

    SysUser(User, "系统用户", Type.Unit),
    SysUserManager(SysUser, "系统用户管理", Type.Bunch),
    SysUserManagerSelect(SysUserManager, "系统用户查询", Type.Function),
    SysUserManagerAdd(SysUserManager, "系统用户新增", Type.Function),
    SysUserManagerUpdate(SysUserManager, "系统用户修改", Type.Function),
    SysUserManagerDelete(SysUserManager, "系统用户删除", Type.Function),

    NormalUser(User, "普通用户", Type.Unit),
    NormalUserManager(NormalUser, "普通用户管理", Type.Bunch),
    NormalUserManagerSelect(NormalUserManager, "普通用户查询", Type.Function),
    NormalUserManagerAdd(NormalUserManager, "普通用户新增", Type.Function),
    NormalUserManagerUpdate(NormalUserManager, "普通用户修改", Type.Function),
    NormalUserManagerDelete(NormalUserManager, "普通用户删除", Type.Function),

    // 订单管理
    Order("order", "订单", Type.Module),

    OrderConfirm(Order, "下单管理", Type.Unit),
    OrderConfirmKill(OrderConfirm, "秒杀下单管理", Type.Bunch),
    OrderConfirmKillAdd(OrderConfirmKill, "秒杀订单新增", Type.Function),
    OrderConfirmKillDelete(OrderConfirmKill, "秒杀订单删除", Type.Function),

    OrderConfirmNormal(OrderConfirm, "普通下单管理", Type.Bunch),
    OrderConfirmNormalAdd(OrderConfirmNormal, "普通订单新增", Type.Function),
    OrderConfirmNormalDelete(OrderConfirmNormal, "普通订单删除", Type.Function),

    // ...其他
    ;


    /**
     * 功能权限类型
     */
    public enum Type {
        Module, // 功能模块
        Unit, // 功能单元
        Bunch, // 功能接口集
        Function, // 功能接口
    }

    // 模块
    private final String module;
    // 名称
    private final String title;
    // 父
    private final AuthPermission parent;
    // 类型
    private final Type type;

    AuthPermission(AuthPermission parent, String title, Type type) {
        this(parent.module, parent, title, type);
    }

    AuthPermission(String title, String module, Type type) {
        this(module, null, title, type);
    }

    AuthPermission(String module, AuthPermission parent, String title, Type type) {
        this.module = module;
        this.title = title;
        this.parent = parent;
        this.type = type;
    }

    public String getModule() {
        return module;
    }

    public String getTitle() {
        return title;
    }

    public AuthPermission getParent() {
        return parent;
    }

    public Type getType() {
        return type;
    }
}
import org.springframework.util.CollectionUtils;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * 接口权限管理类
 */
public class AuthPermissionManagement {

    private static List<AuthArchetype> permissionTree = new ArrayList<>();

    private static Map<AuthPermission, AuthArchetype> permissionMap = new HashMap<>();

    static {
        // 递归设置树
        // 设置子树
        recursePermissions(permissionTree, permissionMap, null);
    }

    public static void main(String[] args) {
    	// 获取权限树(到时候给前端展示树)
        System.out.println(Json.toJson(getPermissionTree()));

		// 校验权限
        System.out.println(checkPermission(AuthPermission.NormalUserManagerDelete, AuthPermission.NormalUser));
        System.out.println(checkPermission(AuthPermission.NormalUserManagerDelete, AuthPermission.OrderConfirm));
        System.out.println(checkPermission(AuthPermission.NormalUserManagerDelete, AuthPermission.NormalUserManagerDelete));

    }

    /**
     * 校验权限 递归
     * @param userPermission 用户角色权限
     * @param interfacePermission 接口权限
     * @return
     */
    public static boolean checkPermission(AuthPermission userPermission, AuthPermission interfacePermission) {
        if (userPermission == interfacePermission) {
            return true;
        }
        // 有子接口权限也可
        AuthArchetype authArchetype = permissionMap.get(interfacePermission);
        if (authArchetype == null) {
            return false;
        }
        return checkChildrenPermission(userPermission, authArchetype);
    }

    private static boolean checkChildrenPermission(AuthPermission userPermission, AuthArchetype authArchetype) {
        if (authArchetype.getName().equals(userPermission.name())) {
            return true;
        }
        if (!CollectionUtils.isEmpty(authArchetype.getSubPermissions())) {
            for (AuthArchetype subPermission : authArchetype.getSubPermissions()) {
                if (subPermission.getName().equals(userPermission.name())) {
                    return true;
                }
                // 递归
                if (checkChildrenPermission(userPermission, subPermission)) {
                    return true;
                }
            }
        }
        return false;
    }

    // 获取权限树
    public static List<AuthArchetype> getPermissionTree() {
        return permissionTree;
    }

    private static void recursePermissions(List<AuthArchetype> permissionTree, Map<AuthPermission, AuthArchetype> permissionMap, AuthPermission current) {
        for (AuthPermission permission : AuthPermission.values()) {
            if (permission.getParent() == current) {
                AuthArchetype permissionArchetype = new AuthArchetype(permission);
                if (current == null) {
                    permissionTree.add(permissionArchetype);
                } else {
                    permissionMap.get(current).addSubPermission(permissionArchetype);
                }
                permissionMap.put(permission, permissionArchetype);
                recursePermissions(permissionTree, permissionMap, permission);

            }

        }
    }


    public static class AuthArchetype {
        // name
        private String name;
        // 模块
        private String module;
        // 名称
        private String title;
        // 父
        private AuthPermission parent;
        // 类型
        private AuthPermission.Type type;
        // 子
        private List<AuthArchetype> subPermissions;

        public AuthArchetype(AuthPermission permission) {
            this.name = permission.name();
            this.module = permission.getModule();
            this.title = permission.getTitle();
            this.parent = permission.getParent();
            this.type = permission.getType();
        }

        public void addSubPermission(AuthArchetype subPermission) {
            if (this.subPermissions == null) {
                this.subPermissions = new ArrayList<>();
            }
            this.subPermissions.add(subPermission);
        }

        public String getName() {
            return name;
        }

        public void setName(String name) {
            this.name = name;
        }

        public String getModule() {
            return module;
        }

        public void setModule(String module) {
            this.module = module;
        }

        public String getTitle() {
            return title;
        }

        public void setTitle(String title) {
            this.title = title;
        }

        public AuthPermission getParent() {
            return parent;
        }

        public void setParent(AuthPermission parent) {
            this.parent = parent;
        }

        public AuthPermission.Type getType() {
            return type;
        }

        public void setType(AuthPermission.Type type) {
            this.type = type;
        }

        public List<AuthArchetype> getSubPermissions() {
            return subPermissions;
        }

        public void setSubPermissions(List<AuthArchetype> subPermissions) {
            this.subPermissions = subPermissions;
        }
    }
}

2、自定义AOP注解

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

@Target({ElementType.METHOD, ElementType.TYPE})
@Retention(RetentionPolicy.RUNTIME)
public @interface Auth {

    AuthPermission[] value() default {};
}

3、AOP切面类(也可以用拦截器实现)

import org.apache.commons.lang3.ArrayUtils;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;

import java.lang.reflect.Method;

@Aspect
@Component
public class AuthAdvice {

    /**
     * 我们希望的是如果方法上有注解,则对方法进行限制,若方法上无注解,单是类上有注解,那么类上的权限注解对该类下所有的接口生效。
     */
    @Around("@annotation(com.qstcloud.athena.opencourse.auth.Auth) || @within(com.qstcloud.athena.opencourse.auth.Auth)")
    public Object preAuth(ProceedingJoinPoint point) throws Throwable {
        if (handleAuth(point)) {
            return point.proceed();
        }
        throw new RuntimeException("权限不足,请求被拒绝");
    }

    /**
     * 逻辑判断,返回true or false
     * true :权限校验通过
     * false :权限校验不通过
     */
    private boolean handleAuth(ProceedingJoinPoint point) {
        MethodSignature ms = point.getSignature() instanceof MethodSignature ? (MethodSignature) point.getSignature() : null;
        Method method = ms.getMethod();
        // 读取权限注解,优先方法上,没有则读取类
        Auth Annotation = getAnnotation(method, Auth.class);


        // 判断权限
        AuthPermission[] authPermissions = Annotation.value();
        if (ArrayUtils.isEmpty(authPermissions)) {
            // 没有权限树,登录就可以访问
            return checkLogin();
        }
        // 校验当前登录用户,是否包含其中权限之一
        return checkHasPermission(authPermissions);

    }

    /**
     * 校验当前登录用户,是否包含其中权限之一
     */
    private boolean checkHasPermission(AuthPermission[] authPermissions) {
//        User user = UserUtil.getCurrentUser();
//        Role role = user.getRole(); // 获取角色

        // TODO 判断角色中的权限,是否包含接口的权限
        return ArrayUtils.contains(authPermissions, AuthPermission.NormalUserManagerUpdate);
    }

    /**
     * 判断是否登录
     */
    private boolean checkLogin() {
        // TODO 从redis或者session中判断是否登录
        return true;
    }

    /**
     * 读取权限注解,优先方法上,没有则读取类
     */
    private Auth getAnnotation(Method method, Class<Auth> authClass) {
        Auth annotation = method.getAnnotation(authClass);
        if (annotation != null) {
            return annotation;
        }

        annotation = method.getDeclaringClass().getAnnotation(authClass);
        return annotation;
    }
}

4、测试一下

@RestController
@RequestMapping("/test")
@Auth
public class TestController {


    @GetMapping("/test1")
    public String test1() {
        return "success";
    }

    @GetMapping("/test2")
    @Auth(AuthPermission.NormalUserManagerDelete)
    public String test2() {
        return "success";
    }

    @GetMapping("/test3")
    @Auth(AuthPermission.NormalUserManagerUpdate)
    public String test3() {
        return "success";
    }
}