基于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";
    }
}
相关推荐
2401_891409263 小时前
量化数据13:港股分钟K线、Level2逐笔Tick、十档盘口和港股通历史数据的批量提取
数据库
1024奇点8 小时前
【仓颉语言入门 · 第二篇 第 22 课】
开发语言·ide·开源
w-w0w-w9 小时前
SQL注入
网络·数据库·sql
Warren2Lynch9 小时前
C4 模型详解:利用 Visual Paradigm 可视化软件架构的综合指南
开发语言·uml
tianyuanwo9 小时前
NetBox 数据库详解:当“关系型数据库”被装进一个网络运维的领域模型里
运维·数据库·netbox
一条泥憨鱼9 小时前
【计算机网络】短连接、长连接与连接池:从 TCP 握手到池化复用的完整链路
网络·数据库·网络协议·tcp/ip·计算机网络
天河归来9 小时前
第三方接口不稳定,如何让核心任务继续完成
java·大数据
IvorySQL9 小时前
深入 PostgreSQL WAL:从崩溃恢复到逻辑复制
数据库·postgresql
Knight_AL10 小时前
Java 文件下载时文件名中的 `#` 被转换成 `%23`?一次 URL 编码与解码问题排查
java·开发语言
FRing0310 小时前
vue2干货总结2(v-bind、v-for、v-model、指令修饰符、v-bind操作class和style)
开发语言·前端·vue.js·vue2