likes
comments
collection
share

Spring AOP实现鉴权功能Spring AOP(面向切面编程)是Spring框架的一部分,用于在不改变业务逻辑的情

作者站长头像
站长
· 阅读数 50

一、AOP概述以及思想

Spring AOP(面向切面编程)是Spring框架的一部分,用于在不改变业务逻辑的情况下,为应用程序添加横切关注点(cross-cutting concerns)。横切关注点是指那些在多个地方都需要实现的功能,例如日志记录、安全控制、事务管理等。

通俗来说:Spring AOP是一种编程方式,用于在不改变业务逻辑的前提下,轻松地为应用程序添加日志记录、事务管理等公共功能。

为什么要用AOP来实现?

  1. 如果有好多个接口都鉴权,那这几个接口里面都要加上鉴权的逻辑。代码比较冗余,而且如果这个鉴权的逻辑要修改的话,所有用到的地方都要同步修改,非常麻烦,不好维护。
  2. 使用切面来把这些接口增强以后,我只需要在用到的地方加上一个注解,功能就实现了,修改起来也方便。

一般来说,配合自定义注解来实现;在需要增加公共功能的方法上打上注解即可;Spring AOP实现鉴权功能Spring AOP(面向切面编程)是Spring框架的一部分,用于在不改变业务逻辑的情

下面,我们来看看自定义注解如何实现

二、自定义注解

需要自定义注解,而这就需要用到元注解;元注解的作用就是用于定义其它的注解

@Retention

@Retention (opens new window)指明了注解的保留级别。

@Retention 源码:

 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
 public @interface Retention {
     RetentionPolicy value();
 }

RetentionPolicy 是一个枚举类型,它定义了被 @Retention 修饰的注解所支持的保留级别:

  • RetentionPolicy.SOURCE - 标记的注解仅在源文件中有效,编译器会忽略。
  • RetentionPolicy.CLASS - 标记的注解在 class 文件中有效,JVM 会忽略。
  • RetentionPolicy.RUNTIME - 标记的注解在运行时有效。

@Retention 示例:

 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 public @interface Column {
     public String name() default "fieldName";
     public String setFuncName() default "setField";
     public String getFuncName() default "getField";
     public boolean defaultDBValue() default false;
 }

@Documented

@Documented (opens new window)表示无论何时使用指定的注解,都应使用 Javadoc(默认情况下,注释不包含在 Javadoc 中)。更多内容可以参考:Javadoc tools page (opens new window)

@Documented 示例:

 @Target(ElementType.FIELD)
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface Column {
     public String name() default "fieldName";
     public String setFuncName() default "setField";
     public String getFuncName() default "getField";
     public boolean defaultDBValue() default false;
 }

@Target

@Target (opens new window)指定注解可以修饰的元素类型。

@Target 源码:

 @Documented
 @Retention(RetentionPolicy.RUNTIME)
 @Target(ElementType.ANNOTATION_TYPE)
 public @interface Target {
     ElementType[] value();
 }

ElementType 是一个枚举类型,它定义了被 @Target 修饰的注解可以应用的范围:

  • ElementType.ANNOTATION_TYPE - 标记的注解可以应用于注解类型。
  • ElementType.CONSTRUCTOR - 标记的注解可以应用于构造函数。
  • ElementType.FIELD - 标记的注解可以应用于字段或属性。
  • ElementType.LOCAL_VARIABLE - 标记的注解可以应用于局部变量。
  • ElementType.METHOD - 标记的注解可以应用于方法。
  • ElementType.PACKAGE - 标记的注解可以应用于包声明。
  • ElementType.PARAMETER - 标记的注解可以应用于方法的参数。
  • ElementType.TYPE - 标记的注解可以应用于类的任何元素。

@Target 示例:

 @Target(ElementType.TYPE)
 public @interface Table {
     /**
      * 数据表名称注解,默认值为类名称
      * @return
      */
     public String tableName() default "className";
 }
 ​
 @Target(ElementType.FIELD)
 public @interface NoDBColumn {}

@Inherited

@Inherited (opens new window)表示注解类型可以被继承(默认情况下不是这样)

表示自动继承注解类型。 如果注解类型声明中存在 @Inherited 元注解,则注解所修饰类的所有子类都将会继承此注解。

🔔 注意:@Inherited 注解类型是被标注过的类的子类所继承。类并不从它所实现的接口继承注解,方法并不从它所覆写的方法继承注解。

此外,当 @Inherited 类型标注的注解的 @RetentionRetentionPolicy.RUNTIME,则反射 API 增强了这种继承性。如果我们使用 java.lang.reflect 去查询一个 @Inherited 类型的注解时,反射代码检查将展开工作:检查类和其父类,直到发现指定的注解类型被发现,或者到达类继承结构的顶层。

 @Inherited
 public @interface Greeting {
     public enum FontColor{ BULE,RED,GREEN};
     String name();
     FontColor fontColor() default FontColor.GREEN;
 }

@Repeatable

@Repeatable (opens new window)表示注解可以重复使用。

以 Spring @Scheduled 为例:

 @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 public @interface Schedules {
     Scheduled[] value();
 }
 ​
 @Target({ElementType.METHOD, ElementType.ANNOTATION_TYPE})
 @Retention(RetentionPolicy.RUNTIME)
 @Documented
 @Repeatable(Schedules.class)
 public @interface Scheduled {
   // ...
 }

应用示例:

 public class TaskRunner {
 ​
     @Scheduled("0 0/15 * * * ?")
     @Scheduled("0 0 12 * ?")
     public void task1() {}
 }

自定义注解格式

注解的语法格式如下:

 public @interface 注解名 {定义体}

三、实践(使用AOP进行鉴权)

1. 先定义自定义注解

 /**
  * 权限校验
  *
 * @author sgx
  */
 @Target(ElementType.METHOD) //应用范围方法体上
 @Retention(RetentionPolicy.RUNTIME) //标记的注解在运行时有效。
 public @interface AuthCheck {
 ​
     /**
      * 必须有某个角色
      *
      * @return
      */
     String mustRole() default "";
 ​
 }
 ​

2. 定义authCheck方法内部处理逻辑

@Around

spring容器中共有五种通知类型:

  • 前置通知: @Before 在方法执行前执行
  • 后置通知 :@After 在方法执行后执行
  • 返回通知 :@AfterReturning在方法执行前执行,无论是否出现异常
  • 异常通知 :@AfterThrowing在方法执行前执行,出现异常则不执行
  • 环绕通知 :@Around可以单独完成以上四个通知

"@annotation(authCheck)")

"@annotation(authCheck)":它会匹配所有被 @AuthCheck 注解标记的方法。也就是说,只有被这个注解标记的方法会被 @Around 通知所拦截。

 /**
  * 权限校验 AOP
  *
 * @author sgx
  */
 @Aspect
 @Component
 public class AuthInterceptor {
 ​
     @Resource
     private UserService userService;
 ​
     /**
      * 执行拦截
      *
      * @param joinPoint
      * @param authCheck
      * @return
      */
     @Around("@annotation(authCheck)")
     public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
         String mustRole = authCheck.mustRole();
         RequestAttributes requestAttributes = RequestContextHolder.currentRequestAttributes();
         HttpServletRequest request = ((ServletRequestAttributes) requestAttributes).getRequest();
         // 当前登录用户
         User loginUser = userService.getLoginUser(request);
         System.out.println(loginUser.getUserName());
         // 必须有该权限才通过
         if (StringUtils.isNotBlank(mustRole)) {
             UserRoleEnum mustUserRoleEnum = UserRoleEnum.getEnumByValue(mustRole);
             if (mustUserRoleEnum == null) {
                 throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
             }
             String userRole = loginUser.getUserRole();
             // 必须有管理员权限
             if (UserRoleEnum.ADMIN.equals(mustUserRoleEnum)) {
                 if (!mustRole.equals(userRole)) {
                     throw new BusinessException(ErrorCode.NO_AUTH_ERROR);
                 }
             }
         }
         // 通过权限校验,放行
         return joinPoint.proceed();
     }
 }

3. 使用

 /**
      * 根据 id 获取用户(仅管理员)
      *
      * @param id
      * @param request
      * @return
      */
 @GetMapping("/get")
 @AuthCheck(mustRole = UserConstant.ADMIN_ROLE)
 public BaseResponse<User> getUserById(long id, HttpServletRequest request) {
     if (id <= 0) {
         throw new BusinessException(ErrorCode.PARAMS_ERROR);
     }
     User user = userService.getById(id);
     ThrowUtils.throwIf(user == null, ErrorCode.NOT_FOUND_ERROR);
     return ResultUtils.success(user);
 }
转载自:https://juejin.cn/post/7418085899714412559
评论
请登录