Spring Boot+Jwt+AOP+自定义注解实现接口的权限控制

​前言

之前在项目中通过自定义拦截器+自定义注解进行权限校验,拦截器代码过于臃肿!!!

于是想到了使用面向切面的方法!!

AOP的概念

Aspect Oreinted Programming 面向切面编程,通过预编译方式或者运行时动态代理的方式,实现程序功能的统一管理和维护的一种技术(AOP是一种思想,并不依赖于某个框架或者编程语言实现)。

为什么使用AOP?

利用AOP可以对 业务逻辑的各部分进行隔离,使程序员更加专注于业务核心逻辑,从而降低代码的耦合度,提供程序可重用性,提高开发的效率(主要应用场景:权限控制,日志记录,性能统计,事务管理,异常处理)

AOP相关的术语

1.目标对象

指需要被 增强的对象,Spring Aop通过代理增强实现 (target)

2.连接点(JoinPoint)

指的是被切面拦截到的点,在Spring当中指的是具体的方法.

3.切入点(pointcut)

表示一组连接点,通过正则表达式,通配符,aspectj切点表达式来进行定义和集中,定义了通知(advice)将要发生的地方. 简单的说:切入点就是我们对 哪些连接点 进行拦截的 定义。

4.通知:(advice)

通知指的是 拦截到连接点之后 要做的事情就是通知(功能增强) 按照分类:前置通知,后置通知,异常通知,环绕通知,最终通知。 前置通知:在目标对象的业务逻辑功能执行之前,发生. 通常用于:日志记录 权限控制

后置返回通知:发生目标功能对象的业务逻辑正常执行完之后,发生. 通常用于:对方法返回值进行处理.

后置异常通知:在目标对象的业务逻辑发生异常时发生 通常用于:项目的异常日志.

后置:不管目标对象的业务逻辑是否发异常,都会被执行. 通常用于:资源释放.

环绕通知:(使用最多),在目标对象的业务逻辑执行之前和之后发生。 通常用于:性能监控,事务管理.

顺序:环绕前置­­>普通前置­­>目标方法执行­­>环绕正常结束/出现异常­­>环绕后置­­>普通后置­­>普通返回或者异常。

5.切面: 切面指的是切入点(多个)和通知(多个)的结合

相关推荐

postcard type="list"\]404\[/postcard

具体实现

第一步:引入aop pom依赖

xml 复制代码
       <!--aop-->
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-aop</artifactId>
        </dependency>

第二步:编写一个自定义注解

java 复制代码
package com.tg.admin.common.annotation;

import java.lang.annotation.*;

/**
 * @Program: admin
 * @ClassName RequiresPermission 
 * @Author: liutao
 * @Description:
 * @Create: 2023-03-20 08:11
 * @Version 1.0
 **/

@Target({ElementType.TYPE, ElementType.METHOD})
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresPermission {
     String roles() default " ";
     String permissions() default " ";
}

第三步:编写一个切面

java 复制代码
package com.tg.admin.common.aop;

import cn.hutool.core.util.StrUtil;
import com.auth0.jwt.JWT;
import com.auth0.jwt.exceptions.JWTDecodeException;
import com.tg.admin.common.Constants;
import com.tg.admin.common.annotation.RequiresPermission;
import com.tg.admin.common.exception.ServiceException;
import com.tg.admin.entity.User;
import com.tg.admin.entity.vo.BtnVo;
import com.tg.admin.service.UserService;
import com.tg.admin.utils.MenuUtil;
import com.tg.admin.utils.RedisUtil;
import lombok.extern.slf4j.Slf4j;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.Signature;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.context.request.ServletRequestAttributes;

import javax.servlet.http.HttpServletRequest;
import java.lang.reflect.Method;
import java.util.Arrays;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;

/**
 * @Program: tg-admin
 * @ClassName PermissionChech
 * @Author: liutao
 * @Description: 角色、权限校验切面
 * @Create: 2023-06-20 18:18
 * @Version 1.0
 **/
@Slf4j
@Aspect
@Component
public class PermissionCheck {
    @Autowired
    private MenuUtil menuUtil;
    @Autowired
    private RedisUtil redisUtil;
    @Autowired
    private UserService userService;


    /***
     * @MethodName: permissionCheckPointCut
     * @description: 定义一个切点
     * @Author: LiuTao
     * @UpdateTime: 2023/6/20 19:34
     **/
    @Pointcut("@annotation(com.tg.admin.common.annotation.RequiresPermission)")
    public void permissionCheckPointCut() {

    }

    /***
     * @MethodName: check
     * @description: 环绕通知
     * @Author: LiuTao
     * @Param: [pjp]
     * @UpdateTime: 2023/6/20 19:34
     * @Return: java.lang.Object
     * @Throw: Throwable
     **/
    @Around("permissionCheckPointCut()")
    public Object check(ProceedingJoinPoint pjp) throws Throwable {
        // 获取请求对象
        HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
        // 记录日志
        log.info("===============系统操作日志===============");
        Signature signature = pjp.getSignature();
        // 请求的类
        String className = pjp.getTarget().getClass().getName();
        String methodName = signature.getName();
        log.info("请求方式:{}", request.getMethod());
        log.info("请求ip:{}", request.getRemoteAddr());
        log.info("请求类方法:{}", signature);
        log.info("请求参数:{}", Arrays.toString(pjp.getArgs()));
        // 权限注解校验
        MethodSignature handlerMethod = (MethodSignature) signature;
        Method method = handlerMethod.getMethod();
        if (method.isAnnotationPresent(RequiresPermission.class)) {
            RequiresPermission auth = method.getAnnotation(RequiresPermission.class);
            String roles = auth.roles();
            String permissions = auth.permissions();

            String token = request.getHeader("token");
            // 认证
            if (StrUtil.isBlank(token)) {
                throw new ServiceException(Constants.CODE_401, "请登录!!!");
            }
            String id;
            try {
                id = JWT.decode(token).getAudience().get(0);
            } catch (JWTDecodeException jwtDecodeException) {
                throw new ServiceException(Constants.CODE_401, "token验证失败,请重新登录");
            }
            User user = userService.getById(id);
            // 校验角色
            if (StrUtil.isNotBlank(roles)) {
                if (!Arrays.asList(roles.split(",")).contains(user.getRole())) {
                    throw new ServiceException(Constants.CODE_403, "当前角色权限不足");
                }
            }
            // 校验权限
            if (StrUtil.isNotBlank(permissions)) {
                List<String> userPermissions = menuUtil
                        .getPermissions(user.getRole())
                        .stream()
                        .map(BtnVo::getPermission)
                        .collect(Collectors.toList());
                if (!new HashSet<>(userPermissions).containsAll(Arrays.asList(permissions.split(",")))) {
                    throw new ServiceException(Constants.CODE_401, "无权限访问资源");
                }
            }
        }
        return pjp.proceed();
    }
}

第四步:在web层使用自定义注解

less 复制代码
    @RequiresPermission(roles = "ROLE_ADMIN")
    @ApiOperation(value = "查询所有用户", httpMethod = "GET")
    @GetMapping
    public Result<User> findAll() {
        List<User> list = userService.findAll();
        log.info("{}", list);
        return Result.success(list);
    }

    @RequiresPermission(permissions = "user:list:page")
    @ApiOperation(value = "分页查询所有用户信息", httpMethod = "GET")
    @GetMapping("/page")
    public Result<User> findPage(@RequestParam Integer pageNum,
                                 @RequestParam Integer pageSize,
                                 @RequestParam String username,
                                 @RequestParam String email,
                                 @RequestParam String address) {
        IPage<User> page = new Page<>(pageNum, pageSize);
        QueryWrapper<User> queryWrapper = new QueryWrapper<>();
        if (!"".equals(username)) {
            queryWrapper.like("username", username);
        }
        if (!"".equals(email)) {
            queryWrapper.like("email", email);
        }
        if (!"".equals(address)) {
            queryWrapper.like("address", address);
        }
        User currentUser = JwtUtil.getCurrentUser();
        System.out.println("当前用户------" + currentUser);
        return Result.success(userService.page(page, queryWrapper));
    }

效果图

使用ROLE_USER 用户访问

然后我们用ROLE_ADMIN 用户访问

结尾

最后完美撒花!!!

相关推荐
chuanauc几秒前
Kubernets K8s 学习
java·学习·kubernetes
一头生产的驴16 分钟前
java整合itext pdf实现自定义PDF文件格式导出
java·spring boot·pdf·itextpdf
YuTaoShao23 分钟前
【LeetCode 热题 100】73. 矩阵置零——(解法二)空间复杂度 O(1)
java·算法·leetcode·矩阵
zzywxc78726 分钟前
AI 正在深度重构软件开发的底层逻辑和全生命周期,从技术演进、流程重构和未来趋势三个维度进行系统性分析
java·大数据·开发语言·人工智能·spring
YuTaoShao3 小时前
【LeetCode 热题 100】56. 合并区间——排序+遍历
java·算法·leetcode·职场和发展
程序员张33 小时前
SpringBoot计时一次请求耗时
java·spring boot·后端
llwszx6 小时前
深入理解Java锁原理(一):偏向锁的设计原理与性能优化
java·spring··偏向锁
云泽野6 小时前
【Java|集合类】list遍历的6种方式
java·python·list
二进制person7 小时前
Java SE--方法的使用
java·开发语言·算法
小阳拱白菜8 小时前
java异常学习
java