当前位置: 首页 > news >正文

Spring Boot中使用注解拦截器实现通用校验器和基于角色的权限注解

通过使用Spring Boot的注解和拦截器,我们可以优雅地实现通用校验器和灵活的权限控制。本文将以电商交易系统为案例,详细讲解如何在Spring Boot中实现支持角色入参的权限校验器,以及如何通过注解拦截器实现通用校验器,提供高拓展性和可维护性的解决方案。

1. 背景介绍

在电商交易系统中,不同的用户角色(如普通用户、商家、管理员)拥有不同的操作权限。例如:

  • 普通用户:可以浏览商品、下单购买。
  • 商家用户:可以上架商品、管理库存。
  • 管理员:可以管理用户、审核商家、处理投诉。

为了确保系统的安全性和业务逻辑的正确性,我们需要对用户的操作进行校验和权限控制。传统的方法可能会在每个接口中添加重复的校验和权限判断代码,既不优雅也不利于维护。为了解决这个问题,我们可以使用Spring Boot的注解和拦截器机制,实现通用校验器和基于角色的权限控制。

2. Maven依赖

首先,我们需要在项目的pom.xml中添加必要的依赖:

<dependencies><!-- Spring Boot Starter Web,用于构建Web应用 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!-- Spring Boot Starter AOP,用于支持切面编程 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-aop</artifactId></dependency><!-- 其他常用依赖,如数据库驱动 --><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-data-jpa</artifactId></dependency><dependency><groupId>mysql</groupId><artifactId>mysql-connector-java</artifactId></dependency>
</dependencies>

引入这些依赖后,我们可以使用Spring Boot的Web和AOP功能来实现我们的需求。

3. 项目结构设计

为了清晰地展示项目的实现,我们可以将项目结构设计如下:

src/main/java/com/example/ecommerce
|-- annotation  // 自定义注解
|   |-- ValidateUser.java       // 通用校验注解
|   |-- RequiresRoles.java      // 支持角色入参的权限注解
|
|-- aspect       // 拦截器和注解处理器
|   |-- ValidationAspect.java   // 校验拦截器
|   |-- AuthorizationAspect.java // 权限拦截器
|
|-- controller   // 控制器层
|   |-- UserController.java
|   |-- ProductController.java
|   |-- OrderController.java
|
|-- service      // 服务层
|   |-- UserService.java
|   |-- ProductService.java
|   |-- OrderService.java
|
|-- model        // 实体类
|   |-- User.java
|   |-- Product.java
|   |-- Order.java
|
|-- util         // 工具类
|   |-- SessionUtil.java        // 会话管理
|
|-- exception    // 自定义异常
|   |-- AuthorizationException.java
|   |-- ValidationException.java

接下来,我们将详细介绍如何实现通用校验器和支持角色入参的权限注解。

4. 自定义注解实现通用校验器

4.1 定义通用校验注解

首先,我们需要定义一个通用的用户校验注解@ValidateUser,用于校验用户的登录状态和信息完整性。

package com.example.ecommerce.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface ValidateUser {String message() default "用户校验失败";
}

该注解可以标记在需要校验用户的控制器方法上,通过AOP在方法执行前进行拦截和校验。

4.2 实现校验拦截器

接下来,实现ValidationAspect类,对使用@ValidateUser注解的方法进行拦截。

package com.example.ecommerce.aspect;import com.example.ecommerce.annotation.ValidateUser;
import com.example.ecommerce.exception.ValidationException;
import com.example.ecommerce.model.User;
import com.example.ecommerce.util.SessionUtil;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.*;
import org.springframework.stereotype.Component;@Aspect
@Component
public class ValidationAspect {@Pointcut("@annotation(com.example.ecommerce.annotation.ValidateUser)")public void validateUserPointcut() {}@Before("validateUserPointcut()")public void doBefore(JoinPoint joinPoint) throws Throwable {User user = SessionUtil.getCurrentUser();// 校验用户是否已登录if (user == null) {throw new ValidationException("用户未登录");}// 校验用户信息是否完整if (user.getUsername() == null || user.getEmail() == null) {throw new ValidationException("用户信息不完整");}}
}

在这里,我们通过SessionUtil.getCurrentUser()获取当前用户(实际应用中可能从Session或Token中获取)。如果用户未登录或信息不完整,抛出自定义的ValidationException异常。

5. 基于角色的权限注解实现

5.1 定义支持角色入参的权限注解

为了实现更加灵活的权限控制,我们需要定义一个支持角色入参的权限注解@RequiresRoles

package com.example.ecommerce.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface RequiresRoles {/*** 需要的角色列表*/String[] roles();String message() default "无权限访问";
}

该注解可以接收一个角色数组roles,表示只有满足这些角色之一的用户才能访问标记的方法。

5.2 实现权限拦截器

接下来,实现AuthorizationAspect类,对使用@RequiresRoles注解的方法进行拦截和权限校验。

package com.example.ecommerce.aspect;import com.example.ecommerce.annotation.RequiresRoles;
import com.example.ecommerce.exception.AuthorizationException;
import com.example.ecommerce.model.User;
import com.example.ecommerce.util.SessionUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Arrays;@Aspect
@Component
public class AuthorizationAspect {@Pointcut("@annotation(com.example.ecommerce.annotation.RequiresRoles)")public void requiresRolesPointcut() {}@Around("requiresRolesPointcut()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {User user = SessionUtil.getCurrentUser();// 校验用户是否已登录if (user == null) {throw new AuthorizationException("用户未登录");}// 获取方法上的注解MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();RequiresRoles requiresRoles = method.getAnnotation(RequiresRoles.class);// 获取需要的角色列表String[] roles = requiresRoles.roles();// 校验用户角色if (Arrays.stream(roles).noneMatch(role -> role.equals(user.getRole()))) {throw new AuthorizationException("无访问权限");}// 权限校验通过,执行方法return joinPoint.proceed();}
}

在这个拦截器中,我们:

  • 获取当前用户,并校验是否已登录。
  • 获取方法上的@RequiresRoles注解,提取需要的角色列表。
  • 校验当前用户的角色是否在需要的角色列表中。
  • 如果权限校验通过,执行目标方法;否则,抛出AuthorizationException异常。

6. 电商交易系统示例

接下来,我们将结合电商交易系统的实际场景,展示如何应用上述的通用校验器和基于角色的权限注解。

6.1 用户管理模块

首先,定义User实体类和SessionUtil工具类。

6.1.1 用户实体类
package com.example.ecommerce.model;public class User {private String username;private String email;private String role; // 用户角色,如 "user", "seller", "admin"// 构造方法、Getter和Setter方法
}
6.1.2 会话管理工具类
package com.example.ecommerce.util;import com.example.ecommerce.model.User;public class SessionUtil {// 模拟获取当前用户的方法public static User getCurrentUser() {// 实际应用中应从Session或Token中获取用户信息return new User("张三", "zhangsan@example.com", "user");}
}

6.2 商品管理模块

在商品管理模块中,不同的操作需要不同的权限。例如:

  • 普通用户:可以查看商品列表。
  • 商家用户:可以添加和修改商品。
  • 管理员:可以删除任何商品。
6.2.1 商品实体类
package com.example.ecommerce.model;public class Product {private Long id;private String name;private Double price;private String description;// 构造方法、Getter和Setter方法
}
6.2.2 商品控制器
package com.example.ecommerce.controller;import com.example.ecommerce.annotation.RequiresRoles;
import com.example.ecommerce.model.Product;
import com.example.ecommerce.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/product")
public class ProductController {@Autowiredprivate ProductService productService;// 所有用户都可以访问@GetMapping("/list")public List<Product> listProducts() {return productService.getAllProducts();}// 商家和管理员可以添加商品@RequiresRoles(roles = {"seller", "admin"})@PostMapping("/add")public String addProduct(@RequestBody Product product) {productService.addProduct(product);return "商品添加成功";}// 商家和管理员可以修改商品@RequiresRoles(roles = {"seller", "admin"})@PutMapping("/update")public String updateProduct(@RequestBody Product product) {productService.updateProduct(product);return "商品更新成功";}// 只有管理员可以删除商品@RequiresRoles(roles = {"admin"})@DeleteMapping("/delete/{id}")public String deleteProduct(@PathVariable Long id) {productService.deleteProduct(id);return "商品删除成功";}
}
6.2.3 商品服务层
package com.example.ecommerce.service;import com.example.ecommerce.model.Product;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class ProductService {// 模拟商品数据库private List<Product> productList;public List<Product> getAllProducts() {// 返回所有商品return productList;}public void addProduct(Product product) {// 添加商品到数据库productList.add(product);}public void updateProduct(Product product) {// 更新商品信息}public void deleteProduct(Long id) {// 从数据库删除商品}
}

6.3 订单管理模块

在订单管理模块中,用户下单需要校验登录状态和用户信息完整性。

6.3.1 订单实体类
package com.example.ecommerce.model;public class Order {private Long id;private Long productId;private Integer quantity;private String status;// 构造方法、Getter和Setter方法
}
6.3.2 订单控制器
package com.example.ecommerce.controller;import com.example.ecommerce.annotation.ValidateUser;
import com.example.ecommerce.model.Order;
import com.example.ecommerce.service.OrderService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;@RestController
@RequestMapping("/order")
public class OrderController {@Autowiredprivate OrderService orderService;// 提交订单,需要校验用户@ValidateUser@PostMapping("/submit")public String submitOrder(@RequestBody Order order) {orderService.submitOrder(order);return "订单提交成功";}// 查询订单,需要登录@ValidateUser@GetMapping("/list")public List<Order> listOrders() {return orderService.getOrdersByUser();}
}
6.3.3 订单服务层
package com.example.ecommerce.service;import com.example.ecommerce.model.Order;
import org.springframework.stereotype.Service;import java.util.List;@Service
public class OrderService {// 模拟订单数据库private List<Order> orderList;public void submitOrder(Order order) {// 提交订单逻辑orderList.add(order);}public List<Order> getOrdersByUser() {// 获取当前用户的订单列表return orderList;}
}

7. 拓展与总结

7.1 拓展思路

  1. 细化权限控制:可以进一步细化权限,例如增加@RequiresPermissions注解,基于具体的操作权限而非角色。

    public @interface RequiresPermissions {String[] permissions();String message() default "无操作权限";
    }
    
  2. 动态权限管理:将权限信息存储在数据库或配置中心,支持动态更新,避免硬编码角色和权限。

  3. 多重校验机制:结合参数校验、业务校验,构建更加完善的校验体系。

  4. 统一异常处理:使用@ControllerAdvice@ExceptionHandler统一处理校验和权限异常,提高代码的可维护性。

    @RestControllerAdvice
    public class GlobalExceptionHandler {@ExceptionHandler(ValidationException.class)public ResponseEntity<String> handleValidationException(ValidationException ex) {return ResponseEntity.status(HttpStatus.BAD_REQUEST).body(ex.getMessage());}@ExceptionHandler(AuthorizationException.class)public ResponseEntity<String> handleAuthorizationException(AuthorizationException ex) {return ResponseEntity.status(HttpStatus.FORBIDDEN).body(ex.getMessage());}
    }
    

为了简化代码并结合@RequiresRoles@ValidateUser的功能,我们可以创建一个新的注解@SecureAction,同时支持用户校验和基于角色的权限控制。通过这个注解,我们可以在一个地方实现对用户的校验和角色权限的判断,避免多次注解的重复使用。

定义新的注解@SecureAction

@SecureAction注解将结合@ValidateUser@RequiresRoles的功能,校验用户的登录状态并判断其角色是否符合要求。我们可以通过注解的参数传递需要的角色列表。

package com.example.ecommerce.annotation;import java.lang.annotation.*;@Target(ElementType.METHOD)
@Retention(RetentionPolicy.RUNTIME)
@Documented
public @interface SecureAction {/*** 需要的角色列表,如果为空则只校验用户登录状态*/String[] roles() default {};String message() default "无权限或用户未登录";
}

在这个注解中,roles参数可选,如果不传递角色则只校验用户的登录状态,传递角色时则会校验用户是否具有指定的角色。

实现拦截器

SecureActionAspect拦截器将同时处理用户校验和角色校验。

package com.example.ecommerce.aspect;import com.example.ecommerce.annotation.SecureAction;
import com.example.ecommerce.exception.AuthorizationException;
import com.example.ecommerce.exception.ValidationException;
import com.example.ecommerce.model.User;
import com.example.ecommerce.util.SessionUtil;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.*;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;import java.lang.reflect.Method;
import java.util.Arrays;@Aspect
@Component
public class SecureActionAspect {@Pointcut("@annotation(com.example.ecommerce.annotation.SecureAction)")public void secureActionPointcut() {}@Around("secureActionPointcut()")public Object doAround(ProceedingJoinPoint joinPoint) throws Throwable {User user = SessionUtil.getCurrentUser();// 校验用户是否已登录if (user == null) {throw new ValidationException("用户未登录");}// 获取方法上的注解MethodSignature signature = (MethodSignature) joinPoint.getSignature();Method method = signature.getMethod();SecureAction secureAction = method.getAnnotation(SecureAction.class);// 获取需要的角色列表String[] roles = secureAction.roles();// 如果定义了角色,则进行角色校验if (roles.length > 0 && Arrays.stream(roles).noneMatch(role -> role.equals(user.getRole()))) {throw new AuthorizationException("无访问权限");}// 用户校验和角色校验通过,执行方法return joinPoint.proceed();}
}

示例:如何使用@SecureAction注解

在控制器方法上使用@SecureAction注解来同时实现用户登录状态和角色的校验:

package com.example.ecommerce.controller;import com.example.ecommerce.annotation.SecureAction;
import com.example.ecommerce.model.Product;
import com.example.ecommerce.service.ProductService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.web.bind.annotation.*;import java.util.List;@RestController
@RequestMapping("/product")
public class ProductController {@Autowiredprivate ProductService productService;// 所有用户都可以查看商品@GetMapping("/list")public List<Product> listProducts() {return productService.getAllProducts();}// 只有商家和管理员可以添加商品@SecureAction(roles = {"seller", "admin"})@PostMapping("/add")public String addProduct(@RequestBody Product product) {productService.addProduct(product);return "商品添加成功";}// 只有管理员可以删除商品@SecureAction(roles = {"admin"})@DeleteMapping("/delete/{id}")public String deleteProduct(@PathVariable Long id) {productService.deleteProduct(id);return "商品删除成功";}
}

7.2 总结

通过本文的介绍,我们学习了如何在Spring Boot中使用自定义注解和拦截器,实现通用的用户校验器和支持角色入参的权限注解。这样的设计具有以下优点:

  • 高可复用性:将校验和权限逻辑抽象为注解和拦截器,避免代码重复。
  • 高可维护性:当需要修改校验或权限逻辑时,只需修改拦截器代码,无需逐个修改业务代码。
  • 高拓展性:可以根据需求灵活添加新的校验规则或权限控制。
  • 增强代码可读性:业务代码中通过注解直观地表达了需要的校验和权限要求。

在实际项目中,合理地使用注解和拦截器,可以大大提高开发效率和代码质量。希望本文的内容对您有所帮助,能够在项目实践中灵活运用。


http://www.mrgr.cn/news/31370.html

相关文章:

  • java之杨辉三角问题
  • A Simple Encoder-Decoder for Open-Vocabulary Semantic Segmentation
  • safepoint是什么?有什么用?
  • anaconda的windows新手安装及配置教程(适用于物联网工程、计算机专业)
  • Matlab R2024B软件安装教程
  • web基础—dvwa靶场(十)XSS
  • 极越联手百度这你受得了吗!SU7还能稳坐“7字辈”头把交椅?
  • 深入探索:深度优先遍历与广度优先遍历的奥秘与应用
  • OpenHarmony(鸿蒙南向开发)——小型系统内核(LiteOS-A)【内核通信机制】下
  • 基于正点原子Linux开发板的智能监控与家电控制系统设计:深度解析Video4Linux和TCP/IP技术栈
  • OpenAI GPT o1技术报告阅读(4)- 填字游戏推理
  • 正点原子阿尔法ARM开发板-IMX6ULL(八)——串口通信(寄存器解释)(补:有源蜂鸣器)
  • POS共识机制简介
  • Viper学习与使用
  • 芹菜麦饭的做法
  • Java流程控制语句——条件控制语句详解(附有流程图)#Java条件控制语句有哪些?#if-else、switch
  • 消息中间件都有哪些
  • 栈的各种接口的实现(C)
  • 《史上最简单的 SpringCloud 教程》
  • k8s自动清理pod脚本分享