当前位置:   article > 正文

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.切面: 切面指的是切入点(多个)和通知(多个)的结合

具体实现

第一步:引入aop pom依赖

  1. <!--aop-->
  2. <dependency>
  3. <groupId>org.springframework.boot</groupId>
  4. <artifactId>spring-boot-starter-aop</artifactId>
  5. </dependency>

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

  1. package com.tg.admin.common.annotation;
  2. import java.lang.annotation.*;
  3. /**
  4. * @Program: admin
  5. * @ClassName RequiresPermission
  6. * @Author: liutao
  7. * @Description:
  8. * @Create: 2023-03-20 08:11
  9. * @Version 1.0
  10. **/
  11. @Target({ElementType.TYPE, ElementType.METHOD})
  12. @Retention(RetentionPolicy.RUNTIME)
  13. @Documented
  14. public @interface RequiresPermission {
  15. String roles() default " ";
  16. String permissions() default " ";
  17. }

第三步:编写一个切面

  1. package com.tg.admin.common.aop;
  2. import cn.hutool.core.util.StrUtil;
  3. import com.auth0.jwt.JWT;
  4. import com.auth0.jwt.exceptions.JWTDecodeException;
  5. import com.tg.admin.common.Constants;
  6. import com.tg.admin.common.annotation.RequiresPermission;
  7. import com.tg.admin.common.exception.ServiceException;
  8. import com.tg.admin.entity.User;
  9. import com.tg.admin.entity.vo.BtnVo;
  10. import com.tg.admin.service.UserService;
  11. import com.tg.admin.utils.MenuUtil;
  12. import com.tg.admin.utils.RedisUtil;
  13. import lombok.extern.slf4j.Slf4j;
  14. import org.aspectj.lang.ProceedingJoinPoint;
  15. import org.aspectj.lang.Signature;
  16. import org.aspectj.lang.annotation.Around;
  17. import org.aspectj.lang.annotation.Aspect;
  18. import org.aspectj.lang.annotation.Pointcut;
  19. import org.aspectj.lang.reflect.MethodSignature;
  20. import org.springframework.beans.factory.annotation.Autowired;
  21. import org.springframework.stereotype.Component;
  22. import org.springframework.web.context.request.RequestContextHolder;
  23. import org.springframework.web.context.request.ServletRequestAttributes;
  24. import javax.servlet.http.HttpServletRequest;
  25. import java.lang.reflect.Method;
  26. import java.util.Arrays;
  27. import java.util.HashSet;
  28. import java.util.List;
  29. import java.util.Objects;
  30. import java.util.stream.Collectors;
  31. /**
  32. * @Program: tg-admin
  33. * @ClassName PermissionChech
  34. * @Author: liutao
  35. * @Description: 角色、权限校验切面
  36. * @Create: 2023-06-20 18:18
  37. * @Version 1.0
  38. **/
  39. @Slf4j
  40. @Aspect
  41. @Component
  42. public class PermissionCheck {
  43. @Autowired
  44. private MenuUtil menuUtil;
  45. @Autowired
  46. private RedisUtil redisUtil;
  47. @Autowired
  48. private UserService userService;
  49. /***
  50. * @MethodName: permissionCheckPointCut
  51. * @description: 定义一个切点
  52. * @Author: LiuTao
  53. * @UpdateTime: 2023/6/20 19:34
  54. **/
  55. @Pointcut("@annotation(com.tg.admin.common.annotation.RequiresPermission)")
  56. public void permissionCheckPointCut() {
  57. }
  58. /***
  59. * @MethodName: check
  60. * @description: 环绕通知
  61. * @Author: LiuTao
  62. * @Param: [pjp]
  63. * @UpdateTime: 2023/6/20 19:34
  64. * @Return: java.lang.Object
  65. * @Throw: Throwable
  66. **/
  67. @Around("permissionCheckPointCut()")
  68. public Object check(ProceedingJoinPoint pjp) throws Throwable {
  69. // 获取请求对象
  70. HttpServletRequest request = ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes())).getRequest();
  71. // 记录日志
  72. log.info("===============系统操作日志===============");
  73. Signature signature = pjp.getSignature();
  74. // 请求的类
  75. String className = pjp.getTarget().getClass().getName();
  76. String methodName = signature.getName();
  77. log.info("请求方式:{}", request.getMethod());
  78. log.info("请求ip:{}", request.getRemoteAddr());
  79. log.info("请求类方法:{}", signature);
  80. log.info("请求参数:{}", Arrays.toString(pjp.getArgs()));
  81. // 权限注解校验
  82. MethodSignature handlerMethod = (MethodSignature) signature;
  83. Method method = handlerMethod.getMethod();
  84. if (method.isAnnotationPresent(RequiresPermission.class)) {
  85. RequiresPermission auth = method.getAnnotation(RequiresPermission.class);
  86. String roles = auth.roles();
  87. String permissions = auth.permissions();
  88. String token = request.getHeader("token");
  89. // 认证
  90. if (StrUtil.isBlank(token)) {
  91. throw new ServiceException(Constants.CODE_401, "请登录!!!");
  92. }
  93. String id;
  94. try {
  95. id = JWT.decode(token).getAudience().get(0);
  96. } catch (JWTDecodeException jwtDecodeException) {
  97. throw new ServiceException(Constants.CODE_401, "token验证失败,请重新登录");
  98. }
  99. User user = userService.getById(id);
  100. // 校验角色
  101. if (StrUtil.isNotBlank(roles)) {
  102. if (!Arrays.asList(roles.split(",")).contains(user.getRole())) {
  103. throw new ServiceException(Constants.CODE_403, "当前角色权限不足");
  104. }
  105. }
  106. // 校验权限
  107. if (StrUtil.isNotBlank(permissions)) {
  108. List<String> userPermissions = menuUtil
  109. .getPermissions(user.getRole())
  110. .stream()
  111. .map(BtnVo::getPermission)
  112. .collect(Collectors.toList());
  113. if (!new HashSet<>(userPermissions).containsAll(Arrays.asList(permissions.split(",")))) {
  114. throw new ServiceException(Constants.CODE_401, "无权限访问资源");
  115. }
  116. }
  117. }
  118. return pjp.proceed();
  119. }
  120. }

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

  1. @RequiresPermission(roles = "ROLE_ADMIN")
  2. @ApiOperation(value = "查询所有用户", httpMethod = "GET")
  3. @GetMapping
  4. public Result<User> findAll() {
  5. List<User> list = userService.findAll();
  6. log.info("{}", list);
  7. return Result.success(list);
  8. }
  9. @RequiresPermission(permissions = "user:list:page")
  10. @ApiOperation(value = "分页查询所有用户信息", httpMethod = "GET")
  11. @GetMapping("/page")
  12. public Result<User> findPage(@RequestParam Integer pageNum,
  13. @RequestParam Integer pageSize,
  14. @RequestParam String username,
  15. @RequestParam String email,
  16. @RequestParam String address) {
  17. IPage<User> page = new Page<>(pageNum, pageSize);
  18. QueryWrapper<User> queryWrapper = new QueryWrapper<>();
  19. if (!"".equals(username)) {
  20. queryWrapper.like("username", username);
  21. }
  22. if (!"".equals(email)) {
  23. queryWrapper.like("email", email);
  24. }
  25. if (!"".equals(address)) {
  26. queryWrapper.like("address", address);
  27. }
  28. User currentUser = JwtUtil.getCurrentUser();
  29. System.out.println("当前用户------" + currentUser);
  30. return Result.success(userService.page(page, queryWrapper));
  31. }

效果图

使用ROLE_USER 用户访问

 

 然后我们用ROLE_ADMIN 用户访问

 

 

 

 结尾

最后完美撒花!!!

本文内容由网友自发贡献,转载请注明出处:【wpsshop博客】
推荐阅读
相关标签
  

闽ICP备14008679号