基于SpringAOP实现自定义接口权限控制

文章目录

一、接口鉴权方案分析

1、接口鉴权方案

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

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

2、角色分配权限树

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

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

二、编码实战

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

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

java 复制代码
/**
 * 接口权限枚举定义类
 */
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;
    }
}
java 复制代码
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注解

java 复制代码
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切面类(也可以用拦截器实现)

java 复制代码
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、测试一下

java 复制代码
@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";
    }
}
相关推荐
姜太小白41 分钟前
【MySQL】 索引优化实战:解决 WHERE 等值 + IS NULL 查询,TEXT 字段报错 1167 的完整指南
数据库·mysql
窝子面1 小时前
手搓最简前后端协作-node
javascript·数据库
吳所畏惧1 小时前
宝塔面板Redis密码修改指南:SSH命令修改 vs 面板UI界面修改,哪个更靠谱?
运维·服务器·数据库·redis·缓存·ssh
DFT计算杂谈1 小时前
无 Root 权限在 Tesla K80 零门槛部署 DeepSeek 大模型
linux·服务器·网络·数据库·机器学习
人邮异步社区1 小时前
怎么把C语言学到精通?
c语言·开发语言
HPFBoy2 小时前
log4net 数据库存日志正确配置
数据库
liuxiaowei32 小时前
【绝版教程】新版MySQL DBA高级实战进阶班 MySQL8.0 姜承尧-腾讯数据库总监
数据库·mysql·dba
心平气和量大福大2 小时前
C#-WPF-控件-TextBox 数据绑定
开发语言·c#·wpf
ttwuai2 小时前
Cursor 生成 CRUD 后,Go 后台接口别只测 200:JWT、RBAC 和 tenant_id 怎么验
开发语言·后端·golang