使用 AOP + 自定义注解优雅实现方法级权限校验

使用 AOP + 自定义注解优雅实现方法级权限校验

_

在日常的后端开发中,接口的权限校验是一个绕不开的话题。相比于在每个 Controller 方法里写一堆 if-else 来判断当前登录用户的角色,使用 自定义注解 + AOP(面向切面编程) 无疑是一种更加优雅、解耦且易于维护的解决方案。

今天这篇文章,我将结合实际项目代码,手把手带你实现一个 @AuthCheck 注解。只要在对应的方法上贴上这个注解,就能轻松搞定“仅管理员可调用”的权限控制!

1. 引入 AOP 依赖

首先,确保你的 Spring Boot 项目中引入了 AOP 相关的 starter 依赖。在 pom.xml 中添加如下配置:

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-aop</artifactId>
</dependency>

2. 定义自定义注解 @AuthCheck

我们需要创建一个自定义注解,用于标记哪些方法需要进行权限校验。

package com.okcl.simpleserver.annotation;

import java.lang.annotation.ElementType;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;

/**
 * 权限校验自定义注解
 */
@Target(ElementType.METHOD) // 作用于方法上
@Retention(RetentionPolicy.RUNTIME) // 在运行时生效
public @interface AuthCheck {
    /**
     * 需要校验的权限角色(例如:"admin")
     *
     * @return 权限字符串
     */
    String hasRole() default "";
}

说明@Target(ElementType.METHOD) 表示该注解只能用在方法上;hasRole 属性用于指定调用该方法所需的角色。

3. 在启动类上开启 AOP

为了让 AOP 切面生效,我们需要在 Spring Boot 的主启动类上加上 @EnableAspectJAutoProxy 注解:

import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.EnableAspectJAutoProxy;

@SpringBootApplication
@EnableAspectJAutoProxy(exposeProxy = true) // 开启 AOP 并暴露代理对象
public class SimpleServerApplication {
    public static void main(String[] args) {
        SpringApplication.run(SimpleServerApplication.class, args);
    }
}

4. 编写核心逻辑:AOP 权限拦截器

这是整个权限校验的核心部分。我们会拦截所有标记了 @AuthCheck 注解的方法,在方法执行前进行角色判断。

注意:下方的 UserRoleEnumErrorCodeBusinessException 以及获取用户信息的 userService.getUserInfo() 均为我项目中的自定义类和方法,大家在实际使用时请替换为自己项目中的用户上下文获取逻辑。

package com.okcl.simpleserver.aop;

import com.okcl.simpleserver.annotation.AuthCheck;
import com.okcl.simpleserver.enums.ErrorCode;
import com.okcl.simpleserver.enums.UserRoleEnum;
import com.okcl.simpleserver.exception.BusinessException;
import com.okcl.simpleserver.model.vo.UserVo;
import com.okcl.simpleserver.service.UserService;
import jakarta.annotation.Resource;
import org.aspectj.lang.ProceedingJoinPoint;
import org.aspectj.lang.annotation.Around;
import org.aspectj.lang.annotation.Aspect;
import org.springframework.stereotype.Component;

@Aspect // 声明这是一个切面类
@Component // 交给 Spring 容器管理
public class AuthInterceptor {

    @Resource
    private UserService userService;

    /**
     * 环绕通知,拦截带有 @AuthCheck 注解的方法
     */
    @Around("@annotation(authCheck)")
    public Object doInterceptor(ProceedingJoinPoint joinPoint, AuthCheck authCheck) throws Throwable {
        // 1. 获取注解上要求的权限
        String requiredRole = authCheck.hasRole();
        UserRoleEnum enumByValue = UserRoleEnum.getEnumByValue(requiredRole);
        
        // 如果注解没有设置角色要求,则直接放行
        if (enumByValue == null) {
            return joinPoint.proceed();
        }
        
        // 2. 获取当前登录用户信息
        UserVo userInfo = userService.getUserInfo();
        Integer currentUserRoleCode = userInfo.getUserRole(); // 当前登录角色的权限编码
        UserRoleEnum userRoleEnum = UserRoleEnum.getEnumByCode(currentUserRoleCode);
        
        // 3. 核心校验逻辑
        if (userRoleEnum == null) {
            // 用户未登录或角色异常
            throw new BusinessException(ErrorCode.NOT_AUTH_ERROR);
        }
        
        // 如果要求是管理员,但当前用户不是管理员,则拦截并抛出无权限异常
        if (UserRoleEnum.ADMIN.equals(enumByValue) && !UserRoleEnum.ADMIN.equals(userRoleEnum)) {
            throw new BusinessException(ErrorCode.NOT_AUTH_ERROR);
        }
        
        // 4. 权限校验通过,继续执行原方法
        return joinPoint.proceed();
    }
}

5. 控制器中的实战使用

一切准备就绪,接下来看看如何在 Controller 中愉快地使用它。我们只需要在对应接口上添加 @AuthCheck(hasRole = "admin") 即可。

import com.okcl.simpleserver.annotation.AuthCheck;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/user")
public class UserController {

    @Resource
    private UserService userService;

    /**
     * 获取用户列表(仅管理员可用)
     *
     * @param userQueryDto 查询条件
     * @return 分页用户数据
     */
    @AuthCheck(hasRole = "admin") // 核心:加上这行注解,非管理员请求将直接被 AOP 拦截抛出异常
    @PostMapping("/list")
    public Result<Page<UserVo>> list(@RequestBody UserQueryDto userQueryDto) {
        Page<UserVo> userVoPage = userService.queryUserList(userQueryDto);
        return ResultUtils.success(userVoPage);
    }
}

总结

通过 自定义注解 + AOP 的方式,我们将权限校验的逻辑从业务代码中彻底剥离了出来。这样做的好处非常明显:

  1. 代码侵入性低:Controller 层代码保持极致的干净整洁。

  2. 复用性强:后续如果有其他接口需要限制管理员访问,只需加一行 @AuthCheck(hasRole = "admin") 即可。

  3. 扩展性好:后续如果想扩展出“VIP用户可见”、“普通用户可见”等逻辑,只需要在 AuthInterceptor 中补充对应的枚举校验即可。

LeetCode 283. 移动零:双指针的巧妙运用 2026-05-26
告别繁琐配置:基于 Auth0 的超轻量级 Java JWT 工具类实现 2026-05-30

评论区