AOP 在调用业务方法前检查权限。权限来源必须是服务端已验证的身份;不能信任请求里自报的角色,也不能把未知角色当作无需权限而放行。
下面用于已经配置 Spring Security 登录的 Spring Boot 3.x 项目,需要 spring-boot-starter-aop 和 spring-boot-starter-security。三个文件放在启动类扫描的 example 包下。
定义角色注解
RequireRole.java:
package example;
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 RequireRole { Role value(); enum Role { USER, ADMIN }}用枚举限制可声明的角色,拼错角色名会在编译阶段失败。
方法执行前校验
RoleAspect.java:
package example;
import org.aspectj.lang.ProceedingJoinPoint;import org.aspectj.lang.annotation.Around;import org.aspectj.lang.annotation.Aspect;import org.springframework.security.access.AccessDeniedException;import org.springframework.security.authentication.AnonymousAuthenticationToken;import org.springframework.security.authentication.AuthenticationCredentialsNotFoundException;import org.springframework.security.core.Authentication;import org.springframework.security.core.context.SecurityContextHolder;import org.springframework.stereotype.Component;
@Aspect@Componentpublic class RoleAspect { @Around("@annotation(required)") public Object check(ProceedingJoinPoint call, RequireRole required) throws Throwable { Authentication user = SecurityContextHolder.getContext().getAuthentication(); if (user == null || !user.isAuthenticated() || user instanceof AnonymousAuthenticationToken) { throw new AuthenticationCredentialsNotFoundException("请先登录"); } String authority = "ROLE_" + required.value().name(); boolean allowed = user.getAuthorities().stream() .anyMatch(granted -> authority.equals(granted.getAuthority())); if (!allowed) throw new AccessDeniedException("没有所需角色"); return call.proceed(); }}这里约定权限为 ROLE_USER / ROLE_ADMIN。ADMIN 不会自动包含 USER;需要层级关系时必须明确配置。
使用与验证
RoleDemoController.java:
package example;
import org.springframework.web.bind.annotation.GetMapping;import org.springframework.web.bind.annotation.RestController;
@RestControllerpublic class RoleDemoController { @RequireRole(RequireRole.Role.ADMIN) @GetMapping("/role-demo") public String read() { return "admin data"; }}用未登录、普通用户、管理员分别请求 /role-demo。前两者不能进入业务方法,管理员得到 admin data。API 的 401 / 403 响应由现有安全过滤链和异常处理共同决定,不能被全局 RuntimeException 处理器转换为成功响应。
同一对象内 this.read() 通常绕过 Spring 代理,切面不能覆盖这种调用。常规项目可以优先使用 Spring Security 的 @EnableMethodSecurity 与 @PreAuthorize("hasRole('ADMIN')"),不必额外维护相同的角色切面。
评论