在 Spring Security 体系里,存在若干用于实现访问控制的注解。这些注解默认处于不可用状态,需借助@EnableMethodSecurity(旧版使用 @EnableGlobalMethodSecurity 注解) 注解开启后,方能投入使用。
当程序运行时,若注解设置的访问条件满足,流程会正常推进;一旦条件不满足,系统会抛出 403(权限不足)异常 。
这类注解的使用位置较为灵活,既能够标注在 Service 接口、Service 方法上,也可用于 Controller 类或者 Controller 的方法上。不过,实际开发里,通常会将其应用在控制器方法中,以此来管控对应接口 URL 的访问权限,决定特定请求能否进入接口逻辑 。
@EnableMethodSecurity 是 Spring Security 6.1 中推荐使用的注解,用于启用方法级别的安全控制。它是 @EnableGlobalMethodSecurity 的替代方案,提供了更简洁、更灵活的配置方式,支持基于注解的权限控制(如 @PreAuthorize、@PostAuthorize)以及与新的授权管理器 API 的集成。
该注解允许在服务层或控制器方法上使用安全注解(如 @PreAuthorize、@Secured),实现细粒度的访问控制。
它默认开启如下核心注解:
@PreAuthorize 方法执行前进行权限校验。
@PostAuthorize 方法执行后基于返回值进行权限校验。
@PreFilter/@PostFilter 对集合参数或返回值进行过滤。
注意:通过参数配置可启用 @Secured 和 JSR-250 的 @RolesAllowed 注解。还可以自定义 AuthorizationManager Bean,实现更复杂的授权逻辑。
@EnableMethodSecurity 注解属性说明:
表示是否启用 JSR-250 标准的安全注解(如 @RolesAllowed、@PermitAll、@DenyAll),默认值 false。例如:
@EnableMethodSecurity(jsr250Enabled = true) public class SecurityConfig { // ... }
指定安全增强的实现方式:
PROXY:基于 Spring AOP 代理(默认),仅支持方法的外部调用。
ASPECTJ:基于 AspectJ 织入,支持所有方法调用(包括内部调用)。
例如:
@EnableMethodSecurity(mode = AdviceMode.ASPECTJ) public class SecurityConfig { // ... }
当多个增强(Advice)应用于同一连接点时,调整安全拦截器的执行顺序。较小的 offset 值表示优先执行。例如:
@EnableMethodSecurity(offset = -1) // 优先执行安全拦截器 public class SecurityConfig { // ... }
默认值为 true,是否启用 Spring Security 的表达式驱动注解:
@PreAuthorize:方法执行前校验权限。
@PostAuthorize:方法执行后校验返回值。
@PreFilter/@PostFilter:过滤集合参数或返回值。
例如:
@EnableMethodSecurity(prePostEnabled = true) // 默认开启 public class SecurityConfig { // ... }
指定代理方式:
false:基于接口的 JDK 动态代理(默认)。
true:基于类的 CGLIB 代理(需引入 CGLIB 依赖)。
例如:
@EnableMethodSecurity(proxyTargetClass = true) // 使用 CGLIB 代理 public class SecurityConfig { // ... }
是否启用 Spring Security 的 @Secured 注解(旧版注解,功能较简单),默认为 false。例如:
@EnableMethodSecurity(securedEnabled = true) public class SecurityConfig { // ... }
下面是上述属性的一个完整配置:
@Configuration @EnableMethodSecurity( prePostEnabled = true, // 启用 @PreAuthorize、@PostAuthorize securedEnabled = false, // 禁用 @Secured jsr250Enabled = true, // 启用 @RolesAllowed mode = AdviceMode.PROXY, // 使用 AOP 代理 proxyTargetClass = false // 使用 JDK 接口代理 ) public class SecurityConfig { // 其他安全配置... }
@Secured 注解是专门用于判断是否具有角色的,能写在方法或类上。@Secured 参数要以 ROLE_ 开头。注意,角色代码注意大小写。
例如:
(1)开启 @Secured 注解功能,如下:
@Configuration @EnableMethodSecurity(securedEnabled = true) public class SecurityConfig { //... }
(2)使用 @Secured 注解验证角色权限,如下:
@RestController public class HelloController { // 用户拥有 normal 角色,可以访问 //@Secured("role_normal") // 用户拥有 admin 或 normal 角色,可以访问 @Secured({"ROLE_admin", "ROLE_normal"}) @GetMapping("/hello") public String hello() { return "Hello, Spring Security!"; } }
@PreAuthorize 和 @PostAuthorize 都是方法或类级别注解。
@PreAuthorize 表示访问方法或类在执行之前先判断权限,大多情况下都是使用这个注解,注解的参数和access() 方法参数取值相同,都是权限表达式。
@PostAuthorize 表示方法或类执行结束后判断权限,此注解很少被使用到(代码都执行了,还校验什么权限呢)。
注意:必须在启动类 @EnableGlobalMethodSecurity 中设置 prePostEnabled = true,如下:
@Configuration @EnableMethodSecurity(prePostEnabled = true) public class SecurityConfig { //... }
注意,prePostEnabled 默认为 true,如下图:
在控制器方法上添加 @PreAuthorize,参数可以是任何 access() 支持的表达式。例如:
@RestController public class HelloController { // 请求前验证,如果有 normal 角色,则允许访问 @PreAuthorize("hasRole('normal')") @GetMapping("/hello") public String hello() { System.out.println("hello()"); return "Hello, Spring Security!"; } }
在控制器方法上添加 @PostAuthorize,参数可以是任何 access() 支持的表达式。例如:
@RestController public class HelloController { // 请求后验证,如果拥有 admin 角色才能访问 @PostAuthorize("hasRole('admin')") @GetMapping("/hello") public String hello() { System.out.println("hello()"); return "Hello, Spring Security!"; } }
运行后如下图:
从上图可知,访问 /hello,后端执行了 hello() 代码,但是并没有通过权限校验。