自学内容网 自学内容网

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 {

    @Autowired
    private 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 {

    @Autowired
    private 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 {

    @Autowired
    private 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中使用自定义注解和拦截器,实现通用的用户校验器和支持角色入参的权限注解。这样的设计具有以下优点:

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

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


原文地址:https://blog.csdn.net/weixin_39996520/article/details/142407364

免责声明:本站文章内容转载自网络资源,如本站内容侵犯了原著者的合法权益,可联系本站删除。更多内容请关注自学内容网(zxcms.com)!