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 拓展思路
-
细化权限控制:可以进一步细化权限,例如增加
@RequiresPermissions
注解,基于具体的操作权限而非角色。public @interface RequiresPermissions {String[] permissions();String message() default "无操作权限"; }
-
动态权限管理:将权限信息存储在数据库或配置中心,支持动态更新,避免硬编码角色和权限。
-
多重校验机制:结合参数校验、业务校验,构建更加完善的校验体系。
-
统一异常处理:使用
@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中使用自定义注解和拦截器,实现通用的用户校验器和支持角色入参的权限注解。这样的设计具有以下优点:
- 高可复用性:将校验和权限逻辑抽象为注解和拦截器,避免代码重复。
- 高可维护性:当需要修改校验或权限逻辑时,只需修改拦截器代码,无需逐个修改业务代码。
- 高拓展性:可以根据需求灵活添加新的校验规则或权限控制。
- 增强代码可读性:业务代码中通过注解直观地表达了需要的校验和权限要求。
在实际项目中,合理地使用注解和拦截器,可以大大提高开发效率和代码质量。希望本文的内容对您有所帮助,能够在项目实践中灵活运用。