做后端开发的同学都知道权限控制是项目的重中之重有些接口只有管理员能访问有些接口需要特定权限才能操作比如“删除用户”“导出数据”“修改配置”。如果在每个 Controller 方法里都写if(role ! admin)if(!hasPermission(user:delete))不仅代码冗余、难以维护还会让核心业务逻辑变得混乱。而用 AOP 实现权限校验只需一行自定义注解就能完成接口的角色和权限控制完全不侵入业务代码干净、优雅、可扩展是企业项目的标配方案。本篇文章我们就用AOP来实现一个实际案例实现系统权限控制。一、权限校验的核心场景不同于简单的角色校验企业级权限校验需要兼顾灵活性和严谨性本次实战覆盖以下核心需求可直接适配大部分项目1.角色校验某些接口仅允许指定角色访问如 admin 管理员、manager 经理支持多角色配置如 roles {admin, manager}2.权限码校验某些接口需要用户拥有指定权限码才能访问如 user:add 新增用户、user:delete 删除用户支持多权限码配置3.校验逻辑灵活配置支持 AND所有条件必须满足、OR满足任一条件即可两种逻辑适配不同业务场景4.超级管理员豁免超级管理员如 super_admin跳过所有权限校验无需重复配置5.统一异常响应无权限时返回统一的 JSON 格式包含错误码、错误信息便于前端统一处理6.不侵入业务代码全程通过 AOP 增强业务方法无需修改降低耦合度7.适配实际项目结合 JWT 解析用户信息替代模拟上下文贴合企业真实开发场景。二、整体设计思路权限校验的核心逻辑是“拦截接口 → 获取用户权限 → 对比校验 → 放行/拦截”用 AOP 实现的整体流程如下步骤清晰、逻辑连贯1.自定义注解创建RequiresPermission注解用于标记接口需要的角色、权限码和校验逻辑2.用户上下文结合 JWT 解析当前登录用户信息获取用户的角色和权限列表替代模拟数据贴合实战3.AOP 切面定义切点拦截所有添加了RequiresPermission注解的方法用环绕通知实现权限校验逻辑4.校验逻辑实现分别实现角色校验、权限码校验支持 AND/OR 逻辑添加超级管理员豁免机制5.全局异常处理捕获权限校验失败的异常返回统一的 JSON 响应避免直接抛出异常暴露接口细节6.接口测试覆盖正常访问、角色不足、权限不足、超级管理员豁免等场景验证校验效果。补充说明AOP 切面的执行顺序很重要权限校验需要优先于日志切面避免无权限请求也记录日志因此给切面添加Order(1)注解值越小执行优先级越高。三、完整代码步骤1导入核心依赖除了 AOP 、 JWT 依赖无需额外导入其他包pom.xml 如下!-- Spring AOP 核心依赖权限校验核心 -- dependency groupIdorg.springframework.boot/groupId artifactIdspring-boot-starter-aop/artifactId /dependency !-- JWT 依赖用于解析用户信息企业实战必备 -- dependency groupIdio.jsonwebtoken/groupId artifactIdjjwt/artifactId version0.9.1/version /dependency !-- 工具包用于 JSON 响应、字符串处理简化代码 -- dependency groupIdcom.alibaba/groupId artifactIdfastjson2/artifactId version2.0.32/version /dependency步骤2自定义权限注解创建RequiresPermission注解用于标记接口需要的角色、权限码和校验逻辑注解的属性设计贴合企业实际需求支持灵活配置import java.lang.annotation.*; /** * 自定义权限校验注解 * Target(ElementType.METHOD)仅作用于方法接口方法 * Retention(RetentionPolicy.RUNTIME)运行时保留AOP 切面可获取注解属性 * Documented生成 API 文档时显示该注解 */ Target(ElementType.METHOD) Retention(RetentionPolicy.RUNTIME) Documented public interface RequiresPermission { /** * 需要的角色如 admin、manager支持多角色配置 * 默认为空数组表示不校验角色 */ String[] roles() default {}; /** * 需要的权限码如 user:add、user:delete支持多权限码配置 * 默认为空数组表示不校验权限码 */ String[] permissions() default {}; /** * 校验逻辑AND所有条件必须满足、OR满足任一条件即可 * 默认为 AND即角色和权限码都满足时才允许访问 */ Logical logical() default Logical.AND; /** * 是否忽略超级管理员校验默认不忽略 * 若为 true超级管理员无需满足角色和权限码直接放行 */ boolean ignoreSuperAdmin() default true; } /** * 校验逻辑枚举清晰区分 AND/OR避免魔法值 */ enum Logical { AND, // 必须全部满足 OR // 满足一个即可 }注解属性说明• roles可配置多个角色如roles {admin, manager}表示只有这两个角色能访问• permissions可配置多个权限码如permissions {user:add, user:edit}表示需要拥有这些权限才能访问• logical控制校验逻辑比如logical Logical.OR表示“角色满足 或 权限满足”即可访问• ignoreSuperAdmin超级管理员豁免开关开启后超级管理员无需校验角色和权限直接放行。步骤3用户上下文 JWT 工具类实际项目中用户信息不会是模拟的而是从请求头的 JWT Token 中解析获取。这里实现完整的 JWT 工具类和用户上下文贴合企业实战3.1 用户实体类存储用户核心信息import lombok.Data; import java.util.List; /** * 用户实体类存储当前登录用户的核心信息 */ Data public class LoginUser { // 用户ID private Long userId; // 用户名 private String username; // 用户角色如 admin、user private String role; // 用户拥有的权限码列表如 [user:list, user:delete] private ListString permissions; // 是否为超级管理员true是false否 private boolean isSuperAdmin; }3.2 JWT 工具类解析 Token、获取用户信息import io.jsonwebtoken.Claims; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import org.springframework.beans.factory.annotation.Value; import org.springframework.stereotype.Component; import java.util.Date; import java.util.List; /** * JWT 工具类企业实战常用用于生成、解析 Token */ Component public class JwtUtils { // 从配置文件读取 JWT 密钥实际项目配置在 application.yml 中 Value(${jwt.secret}) private String secret; // Token 过期时间单位毫秒这里设置为 24 小时 Value(${jwt.expiration}) private Long expiration; /** * 解析 Token获取用户信息核心方法 */ public LoginUser parseToken(String token) { // 1. 解析 Token获取声明信息 Claims claims Jwts.parser() .setSigningKey(secret) .parseClaimsJws(token) .getBody(); // 2. 从声明中提取用户信息封装为 LoginUser 对象 LoginUser loginUser new LoginUser(); loginUser.setUserId(Long.parseLong(claims.get(userId).toString())); loginUser.setUsername(claims.get(username).toString()); loginUser.setRole(claims.get(role).toString()); loginUser.setPermissions((ListString) claims.get(permissions)); loginUser.setSuperAdmin(Boolean.parseBoolean(claims.get(isSuperAdmin).toString())); return loginUser; } /** * 生成 Token可选用于登录接口返回 Token */ public String generateToken(LoginUser loginUser) { Date now new Date(); Date expirationDate new Date(now.getTime() expiration); // 生成 Token将用户核心信息存入声明 return Jwts.builder() .setSubject(loginUser.getUsername()) .claim(userId, loginUser.getUserId()) .claim(role, loginUser.getRole()) .claim(permissions, loginUser.getPermissions()) .claim(isSuperAdmin, loginUser.isSuperAdmin()) .setIssuedAt(now) .setExpiration(expirationDate) .signWith(SignatureAlgorithm.HS512, secret) .compact(); } }3.3 配置文件application.yml添加 JWT 配置# JWT 配置企业实战必备 jwt: secret: springboot-aop-permission-2026 # 密钥实际项目建议用复杂字符串加密存储 expiration: 86400000 # Token 过期时间24小时单位毫秒3.4 用户上下文从请求头获取 Token解析用户信息import org.springframework.web.context.request.RequestContextHolder; import org.springframework.web.context.request.ServletRequestAttributes; import javax.servlet.http.HttpServletRequest; /** * 用户上下文全局获取当前登录用户信息简化代码 */ public class UserContext { // 从请求头获取 Token解析并返回当前登录用户信息 public static LoginUser getCurrentUser() { // 1. 获取当前请求对象 ServletRequestAttributes attributes (ServletRequestAttributes) RequestContextHolder.getRequestAttributes(); HttpServletRequest request attributes.getRequest(); // 2. 从请求头获取 Token请求头keyAuthorization格式Bearer Token String token request.getHeader(Authorization); if (token null || token.isEmpty()) { throw new RuntimeException(未登录请先登录); } // 去除 Token 前缀 Bearer 前端传递时通常会加 if (token.startsWith(Bearer )) { token token.substring(7); } // 3. 解析 Token返回用户信息注入 JWT 工具类 JwtUtils jwtUtils SpringContextUtils.getBean(JwtUtils.class); return jwtUtils.parseToken(token); } // 简化方法获取当前用户角色 public static String getCurrentRole() { return getCurrentUser().getRole(); } // 简化方法获取当前用户权限列表 public static ListString getCurrentPermissions() { return getCurrentUser().getPermissions(); } // 简化方法判断当前用户是否为超级管理员 public static boolean isSuperAdmin() { return getCurrentUser().isSuperAdmin(); } }3.5 补充 Spring 上下文工具类用于在非 Spring 管理类中获取 Beanimport org.springframework.context.ApplicationContext; import org.springframework.context.ApplicationContextAware; import org.springframework.stereotype.Component; /** * Spring 上下文工具类用于在 UserContext 中获取 JwtUtils Bean */ Component public class SpringContextUtils implements ApplicationContextAware { private static ApplicationContext applicationContext; Override public void setApplicationContext(ApplicationContext applicationContext) { SpringContextUtils.applicationContext applicationContext; } // 根据 Bean 类型获取 Bean public static T T getBean(ClassT clazz) { return applicationContext.getBean(clazz); } // 根据 Bean 名称获取 Bean可选 public static Object getBean(String beanName) { return applicationContext.getBean(beanName); } }步骤4AOP 权限校验切面这是本次实战的核心创建切面类实现权限校验的全部逻辑拦截注解标记的接口、获取用户信息、角色校验、权限码校验、超级管理员豁免代码添加详细注释便于理解和修改import org.aspectj.lang.ProceedingJoinPoint; import org.aspectj.lang.annotation.Around; import org.aspectj.lang.annotation.Aspect; import org.aspectj.lang.annotation.Pointcut; import org.aspectj.lang.reflect.MethodSignature; import org.springframework.core.annotation.Order; import org.springframework.stereotype.Component; import java.lang.reflect.Method; import java.util.Arrays; import java.util.List; /** * 权限校验切面核心类 * Aspect标记此类为 AOP 切面 * Component交给 Spring 管理确保 Spring 能扫描到该切面 * Order(1)设置切面执行优先级1 表示优先执行高于日志切面避免无权限请求记录日志 */ Aspect Component Order(1) public class PermissionAspect { // 1. 定义切点拦截所有添加了 RequiresPermission 注解的方法 Pointcut(annotation(com.example.demo.annotation.RequiresPermission)) public void permissionPointcut() {} // 切点方法无实际业务逻辑仅用于标记切点 // 2. 环绕通知包裹目标方法实现权限校验可在方法执行前、执行后处理 Around(permissionPointcut()) public Object checkPermission(ProceedingJoinPoint joinPoint) throws Throwable { // 第一步获取目标方法上的 RequiresPermission 注解 MethodSignature signature (MethodSignature) joinPoint.getSignature(); Method targetMethod signature.getMethod(); RequiresPermission permissionAnno targetMethod.getAnnotation(RequiresPermission.class); // 若注解为空理论上不会出现防止异常直接放行 if (permissionAnno null) { return joinPoint.proceed(); } // 第二步获取当前登录用户的信息从 UserContext 中获取贴合实战 LoginUser currentUser UserContext.getCurrentUser(); String currentRole currentUser.getRole(); ListString currentPermissions currentUser.getPermissions(); boolean isSuperAdmin currentUser.isSuperAdmin(); // 第三步超级管理员豁免校验如果注解开启了豁免开关 if (permissionAnno.ignoreSuperAdmin() isSuperAdmin) { // 超级管理员直接放行无需校验角色和权限 return joinPoint.proceed(); } // 第四步校验角色如果注解配置了需要的角色 boolean roleCheckPass checkRole(permissionAnno, currentRole); if (!roleCheckPass) { throw new PermissionException(403, 无访问角色权限需拥有角色 Arrays.toString(permissionAnno.roles())); } // 第五步校验权限码如果注解配置了需要的权限码 boolean permissionCheckPass checkPermission(permissionAnno, currentPermissions); if (!permissionCheckPass) { throw new PermissionException(403, 无操作权限需拥有权限码 Arrays.toString(permissionAnno.permissions())); } // 第六步所有校验通过执行目标方法核心业务逻辑 return joinPoint.proceed(); } /** * 角色校验逻辑 * param anno 权限注解包含需要的角色 * param currentRole 当前用户角色 * return 校验结果true通过false不通过 */ private boolean checkRole(RequiresPermission anno, String currentRole) { String[] needRoles anno.roles(); // 若注解未配置角色直接通过校验 if (needRoles null || needRoles.length 0) { return true; } // 判断当前用户角色是否在需要的角色列表中 boolean hasTargetRole Arrays.asList(needRoles).contains(currentRole); // 根据校验逻辑AND/OR返回结果 if (anno.logical() Logical.OR) { // OR 逻辑只要拥有一个需要的角色就通过 return hasTargetRole; } else { // AND 逻辑需要拥有所有配置的角色实际中多角色 AND 场景较少此处兼容 return Arrays.stream(needRoles).allMatch(role - role.equals(currentRole)); } } /** * 权限码校验逻辑 * param anno 权限注解包含需要的权限码 * param currentPermissions 当前用户拥有的权限码列表 * return 校验结果true通过false不通过 */ private boolean checkPermission(RequiresPermission anno, ListString currentPermissions) { String[] needPermissions anno.permissions(); // 若注解未配置权限码直接通过校验 if (needPermissions null || needPermissions.length 0) { return true; } // 判断当前用户是否拥有需要的权限码至少一个/全部根据逻辑 if (anno.logical() Logical.OR) { // OR 逻辑拥有任意一个需要的权限码就通过 return Arrays.stream(needPermissions).anyMatch(currentPermissions::contains); } else { // AND 逻辑需要拥有所有配置的权限码 return Arrays.stream(needPermissions).allMatch(currentPermissions::contains); } } }步骤5自定义权限异常区分权限异常和其他异常创建自定义异常类用于区分权限校验失败和其他业务异常便于全局异常处理器精准捕获和返回对应信息import lombok.Data; import lombok.EqualsAndHashCode; /** * 自定义权限异常权限校验失败时抛出 */ Data EqualsAndHashCode(callSuper true) public class PermissionException extends RuntimeException { // 错误码如 403 无权限 private Integer code; // 错误信息 private String message; // 构造方法简化异常抛出 public PermissionException(Integer code, String message) { super(message); this.code code; this.message message; } }步骤6全局异常处理器统一响应格式拦截权限异常和其他异常返回统一的 JSON 格式便于前端统一处理如弹窗提示无权限同时隐藏接口内部异常细节提升安全性import com.alibaba.fastjson2.JSONObject; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RestControllerAdvice; /** * 全局异常处理器统一异常响应 * RestControllerAdvice作用于所有 RestController 注解的接口 */ RestControllerAdvice public class GlobalExceptionHandler { // 拦截自定义权限异常权限校验失败 ExceptionHandler(PermissionException.class) public JSONObject handlePermissionException(PermissionException e) { JSONObject response new JSONObject(); response.put(code, e.getCode()); response.put(msg, e.getMessage()); response.put(data, null); return response; } // 拦截未登录异常从 UserContext 中抛出 ExceptionHandler(RuntimeException.class) public JSONObject handleRuntimeException(RuntimeException e) { JSONObject response new JSONObject(); // 区分未登录和其他运行时异常 if (e.getMessage().contains(未登录)) { response.put(code, 401); response.put(msg, e.getMessage()); } else { response.put(code, 500); response.put(msg, 服务器内部异常请联系管理员); } response.put(data, null); return response; } // 拦截其他异常兜底处理 ExceptionHandler(Exception.class) public JSONObject handleException(Exception e) { JSONObject response new JSONObject(); response.put(code, 500); response.put(msg, 服务器内部异常请联系管理员); response.put(data, null); return response; } }步骤7接口使用示例在 Controller 接口上添加RequiresPermission注解根据业务需求配置角色、权限码和校验逻辑无需修改接口内部业务代码import com.example.demo.annotation.RequiresPermission; import com.example.demo.entity.LoginUser; import com.example.demo.util.UserContext; import org.springframework.web.bind.annotation.GetMapping; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RestController; /** * 测试接口覆盖权限校验多场景 */ RestController RequestMapping(/system) public class SystemController { /** * 场景1仅管理员admin能访问无权限码校验 * 普通用户访问会被拦截提示“无访问角色权限” */ RequiresPermission(roles admin) GetMapping(/user/list) public String userList() { // 核心业务逻辑查询用户列表 return 管理员查询用户列表成功; } /** * 场景2需要拥有 user:delete 权限码才能访问无角色校验 * 无该权限码的用户访问会被拦截 */ RequiresPermission(permissions user:delete) PostMapping(/user/delete) public String deleteUser() { // 核心业务逻辑删除用户 return 删除用户成功; } /** * 场景3角色为 admin 或 拥有 user:export 权限码即可访问OR 逻辑 * 满足任一条件就放行 */ RequiresPermission(roles admin, permissions user:export, logical Logical.OR) GetMapping(/user/export) public String exportUser() { // 核心业务逻辑导出用户数据 return 导出用户数据成功; } /** * 场景4角色为 admin 且 拥有 user:edit 权限码才能访问AND 逻辑 * 必须同时满足两个条件才放行 */ RequiresPermission(roles admin, permissions user:edit, logical Logical.AND) PostMapping(/user/edit) public String editUser() { // 核心业务逻辑修改用户信息 return 修改用户信息成功; } /** * 场景5超级管理员豁免校验即使不满足角色和权限也能访问 * 普通用户访问会被拦截超级管理员直接放行 */ RequiresPermission(roles admin, permissions system:config, ignoreSuperAdmin true) GetMapping(/config) public String getSystemConfig() { // 核心业务逻辑查询系统配置 LoginUser currentUser UserContext.getCurrentUser(); return currentUser.getUsername() 查询系统配置成功; } }四、测试验证为了确保权限校验功能正常我们覆盖 5 种核心场景进行测试模拟不同用户的访问情况验证校验效果测试工具Postman。测试准备创建 3 个测试用户1. 用户1普通用户role userpermissions {user:list, user:query}非超级管理员2. 用户2管理员role adminpermissions {user:add, user:edit}非超级管理员3. 用户3超级管理员role super_adminpermissions {}isSuperAdmin true。测试结果访问接口测试用户测试结果说明/system/user/list普通用户拦截403普通用户角色不是 admin不满足角色校验/system/user/delete管理员拦截403管理员无 user:delete 权限码不满足权限校验/system/user/export普通用户拦截403普通用户既不是 admin也无 user:export 权限/system/user/export管理员放行200管理员角色满足 OR 逻辑无需校验权限码/system/config超级管理员放行200超级管理员豁免校验直接放行测试结论所有场景均符合预期权限校验生效无权限时返回统一的 403 响应超级管理员豁免机制正常不影响核心业务逻辑。文末小结SpringBoot AOP 实现权限校验是企业项目中最优雅、最高效的方案核心逻辑就是「注解标记 AOP 拦截 上下文校验」全程不侵入业务代码可扩展性极强。如果你在实战中遇到问题比如切面不生效、JWT 解析失败、数据权限扩展欢迎在评论区留言交流一起避坑、一起进步别忘了点赞在看收藏三连关注我解锁更多 SpringBoot 实战干货下期再见❤️