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 用户访问

结尾

最后完美撒花!!!

相关推荐
monkey_meng7 分钟前
【Rust中的迭代器】
开发语言·后端·rust
余衫马10 分钟前
Rust-Trait 特征编程
开发语言·后端·rust
monkey_meng14 分钟前
【Rust中多线程同步机制】
开发语言·redis·后端·rust
七星静香15 分钟前
laravel chunkById 分块查询 使用时的问题
java·前端·laravel
Jacob程序员16 分钟前
java导出word文件(手绘)
java·开发语言·word
ZHOUPUYU16 分钟前
IntelliJ IDEA超详细下载安装教程(附安装包)
java·ide·intellij-idea
stewie620 分钟前
在IDEA中使用Git
java·git
Elaine20239135 分钟前
06 网络编程基础
java·网络
G丶AEOM36 分钟前
分布式——BASE理论
java·分布式·八股
落落鱼201337 分钟前
tp接口 入口文件 500 错误原因
java·开发语言