springboot+自定义注解+AOP实现权限控制(二)

文末有下载链接!!!

在上文《springboot+自定义注解+AOP实现权限控制(一)》中,简单介绍了自定义注解和AOP的作用,本文开始从代码角度详细解释该如何使用。

实现效果

    我想要这样一个效果,如:在某个接口加上自定义的权限注解,表示只有拥有此权限的用户才能访问。如A用户访问到add接口,如果有add接口的权限,允许访问,否则返回权限不足信息并不允许访问。其中返回信息为了美化,我通过全局异常来捕获。

设计思路:

1. 用户分为四种权限,管理员,添加和修改员,删除员,普通用户(只有查看权限);

2. 通过访问product类的增删改查接口来验证权限是否成功;

3. 流程:

(1)接口添加对应的注解,如add接口设置@Permission(value=add),表示拥有add权限的人才能访问

(2)用户登录后产生一个token,token包含用户的id信息(此步省略。假设token即userId)。

(3)访问接口,通过AOP拦截到此用户的登录信息,到数据库查询该用户的对应权限permissons

(4)查看permissons中是否包含注解的属性值value=add,

(5)如果包含,允许访问;否则抛出异常,不允许访问。

4. 关键点

(1)自定义权限注解如何设计

(2)AOP的切面如何编写

(3)用户-角色-权限如何设计

一. 搭建基本结构

springboot+自定义注解+AOP实现权限控制(二)_第1张图片

二. 设计用户-角色-权限表结构

添加测试用户,userId=1为管理员,userId=2可以添加或修改,userId=3可以删除,其他只有查看权限。

用户表-角色表-权限表分别用中间表来关联外键。

三. 自定义注解

首先我定义了几个常量表示CRUD的权限

public class PermissionConsts {

    /**
     * 查看权限
     */
    public static final String R = "R_PERMISSION";

    /**
     * 添加权限
     */
    public static final String C = "C_PERMISSION";

    /**
     * 修改权限
     */
    public static final String U = "U_PERMISSION";

    /**
     * 删除权限
     */
    public static final String D = "D_PERMISSION";
}

自定义注解:

@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
public @interface MyPermission {

    /**
     * 默认只有查看权限
     * @return
     */
    String value() default PermissionConsts.R;
}

四. AOP切面

@Aspect
@Component
@Slf4j
public class PermissionAspect {

    @Resource
    private UserDao userDao;

    /**
     * 目标方法
     */
    @Pointcut("@annotation(com.mac.annotation.MyPermission)")
    private void permission() {

    }

    /**
     * 目标方法调用之前执行
     */
    @Before("permission()")
    public void doBefore() {
        System.out.println("================== step 2: before ==================");
    }

    /**
     * 目标方法调用之后执行
     */
    @After("permission()")
    public void doAfter() {
        System.out.println("================== step 4: after ==================");
    }

    /**
     * 环绕
     * 会将目标方法封装起来
     * 具体验证业务数据
     */
    @Around("permission()")
    public Object doAround(ProceedingJoinPoint proceedingJoinPoint) throws Throwable {
        System.out.println("================== step 1: around ==================");
        long startTime = System.currentTimeMillis();
        /*
         * 获取当前http请求中的token
         * 解析token :
         * 1、token是否存在
         * 2、token格式是否正确
         * 3、token是否已过期(解析信息或者redis中是否存在)
         * */
        ServletRequestAttributes attributes = (ServletRequestAttributes) RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = attributes.getRequest();

        String token = request.getHeader("token");
        if (StringUtils.isEmpty(token)) {
            throw new RuntimeException("非法请求,无效token");
        }
        // 校验token的业务逻辑

        //假设token里只是一个userId,查询到他有删除和查看的权限,没有添加和修改的权限
        // 解析token之后,获取当前用户的账号信息,查看它对应的角色和权限信息
        //String userId = parse(token);
        List codes = userDao.findPermissionCodeByUserId(token);
        List permissionCodes = codes.stream().map(UserPermissionDto::getPermissionCode).collect(Collectors.toList());
        /*
         * 获取注解的值,并进行权限验证
         * */
        Method method = ((MethodSignature) proceedingJoinPoint.getSignature()).getMethod();
        MyPermission myPermission = method.getAnnotation(MyPermission.class);

        String value = myPermission.value();
        // 将注解的值和token解析后的值进行对比,查看是否有该权限,如果权限通过,允许访问方法;否则不允许,并抛出异常
        if(!permissionCodes.contains(value)){
            throw new RuntimeException("对不起,您没有权限访问!");
        }
        // 执行具体方法
        Object result = proceedingJoinPoint.proceed();

        long endTime = System.currentTimeMillis();

        /*
         * 记录相关执行结果
         * 可以存入MongoDB 后期做数据分析
         * */
        // 打印请求 url
        System.out.println("URL            : " + request.getRequestURL().toString());
        // 打印 Http method
        System.out.println("HTTP Method    : " + request.getMethod());
        // 打印调用 controller 的全路径以及执行方法
        System.out.println("controller     : " + proceedingJoinPoint.getSignature().getDeclaringTypeName());
        // 调用方法
        System.out.println("Method         : " + proceedingJoinPoint.getSignature().getName());
        // 执行耗时
        System.out.println("cost-time      : " + (endTime - startTime) + " ms");

        return result;

    }

}

五. 接口添加注解

@RestController
@RequestMapping(value = "/product")
public class ProductController {

    @Resource
    private ProductService productService;

    @GetMapping(value = "/findAll")
    @MyPermission(value = PermissionConsts.R)
    public Result> findAll(){
        return productService.findAll();
    }

    @PostMapping(value = "/add")
    @MyPermission(value = PermissionConsts.C)
    public Result add(@RequestBody ProductAddParam productParam){
        return productService.add(productParam);
    }

    @PostMapping(value = "/update")
    @MyPermission(value = PermissionConsts.U)
    public Result update(@RequestBody ProductUpdateParam productParam){
        return productService.update(productParam);
    }

    @GetMapping(value = "/delete")
    @MyPermission(value = PermissionConsts.D)
    public Result delete(@RequestParam Long id){
        return productService.delete(id);
    }
    
}

六. 其他代码可以查看文末的下载链接

七. 测试

1. 查看接口。

测试token=1到6(后期可用jwt优化token,这里简单设置为token=userId)的用户,发现数据库每个用户都可以访问。但userId不存在的用户,如token=100,则无法访问接口。

springboot+自定义注解+AOP实现权限控制(二)_第2张图片

2. 添加/修改接口

添加/修改产品,userId=3的用户无法删除,因为数据库查询到权限不足;只有userId=2的用户可以添加/修改

3. 删除接口

删除productId=1的产品,userId=2的用户无法删除,因为数据库查询到权限不足;只有userId=1或3的用户可以删除

springboot+自定义注解+AOP实现权限控制(二)_第3张图片

springboot+自定义注解+AOP实现权限控制(二)_第4张图片

 

github链接:https://github.com/laoyog/customize-permission

CSDN链接:https://download.csdn.net/download/byteArr/12104797

你可能感兴趣的:(SpringBoot,springboot,自定义注解,AOP,权限控制)