Spring Boot+Jwt+AOP+自定义注解实现接口的权限控制
本文最后更新于2023.07.21-01:18
,某些文章具有时效性,若有错误或已失效,请在下方留言或联系涛哥。
前言
之前在项目中通过自定义拦截器+自定义注解进行权限校验,拦截器代码过于臃肿!!!
于是想到了使用面向切面的方法!!
AOP的概念
Aspect Oreinted Programming 面向切面编程,通过预编译方式或者运行时动态代理的方式,实现程序功能的统一管理和维护的一种技术(AOP是一种思想,并不依赖于某个框架或者编程语言实现)。
为什么使用AOP?
利用AOP可以对 业务逻辑的各部分进行隔离,使程序员更加专注于业务核心逻辑,从而降低代码的耦合度,提供程序可重用性,提高开发的效率(主要应用场景:权限控制,日志记录,性能统计,事务管理,异常处理)
AOP相关的术语
1.目标对象
指需要被 增强的对象,Spring Aop通过代理增强实现 (target)
2.连接点(JoinPoint)
指的是被切面拦截到的点,在Spring当中指的是具体的方法.
3.切入点(pointcut)
表示一组连接点,通过正则表达式,通配符,aspectj切点表达式来进行定义和集中,定义了通知(advice)将要发生的地方. 简单的说:切入点就是我们对 哪些连接点 进行拦截的 定义。
4.通知:(advice)
通知指的是 拦截到连接点之后 要做的事情就是通知(功能增强) 按照分类:前置通知,后置通知,异常通知,环绕通知,最终通知。 前置通知:在目标对象的业务逻辑功能执行之前,发生. 通常用于:日志记录 权限控制
后置返回通知:发生目标功能对象的业务逻辑正常执行完之后,发生. 通常用于:对方法返回值进行处理.
后置异常通知:在目标对象的业务逻辑发生异常时发生 通常用于:项目的异常日志.
后置:不管目标对象的业务逻辑是否发异常,都会被执行. 通常用于:资源释放.
环绕通知:(使用最多),在目标对象的业务逻辑执行之前和之后发生。 通常用于:性能监控,事务管理.
顺序:环绕前置>普通前置>目标方法执行>环绕正常结束/出现异常>环绕后置>普通后置>普通返回或者异常。
5.切面: 切面指的是切入点(多个)和通知(多个)的结合
相关推荐
具体实现
第一步:引入aop pom依赖
<!--aop-->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-aop</artifactId>
</dependency>
第二步:编写一个自定义注解
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 " ";
}
第三步:编写一个切面
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层使用自定义注解
@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 用户访问
结尾
最后完美撒花!!!
作者:涛哥
链接:https://ltbk.net/back/spring_family/spring-boot/article/1539.html
文章版权归作者所有,未经允许请勿转载。