基于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";
    }
}
相关推荐
马克Markorg3 小时前
常见的向量数据库和具有向量数据库能力的数据库
数据库
冷雨夜中漫步3 小时前
Python快速入门(6)——for/if/while语句
开发语言·经验分享·笔记·python
JH30734 小时前
SpringBoot 优雅处理金额格式化:拦截器+自定义注解方案
java·spring boot·spring
m0_736919105 小时前
C++代码风格检查工具
开发语言·c++·算法
Coder_Boy_5 小时前
技术让开发更轻松的底层矛盾
java·大数据·数据库·人工智能·深度学习
2501_944934735 小时前
高职大数据技术专业,CDA和Python认证优先考哪个?
大数据·开发语言·python
helloworldandy5 小时前
使用Pandas进行数据分析:从数据清洗到可视化
jvm·数据库·python
invicinble5 小时前
对tomcat的提供的功能与底层拓扑结构与实现机制的理解
java·tomcat
较真的菜鸟5 小时前
使用ASM和agent监控属性变化
java
黎雁·泠崖6 小时前
【魔法森林冒险】5/14 Allen类(三):任务进度与状态管理
java·开发语言