bigywater.tech
Spring · 108

方法级安全:@PreAuthorize 与代理

@PreAuthorize 为什么有时候不生效?类内部调用会不会绕过?@PostAuthorize 是方法执行前还是执行后检查?@Secured 还能用吗?这篇在 Spring Boot 4.0.8、Spring Security 7 上用同一个 Service 把各种情况都跑一遍。

绕过
同一个类里 this.admin() 自调用不经过代理,@PreAuthorize 没有执行
@Secured 无效
默认的 @EnableMethodSecurity 下,@Secured 与 @RolesAllowed 标的方法谁都能调
2 而不是 1
@PreFilter 的参数是不可变列表时,没有过滤也没有报错

代理与盲区

实测 · 本机执行输出
  Svc 的实际类型:Svc$$SpringCGLIB$$0;是不是 AOP 代理:true;是不是 CGLIB 代理:true
  alice(ROLE_USER)调 admin() ⇒ 抛 AuthorizationDeniedException:Access Denied
  root(ROLE_ADMIN)调 admin() ⇒ admin 的数据
  没有登录(SecurityContext 里没有 Authentication)调 admin() ⇒ 抛 AuthenticationCredentialsNotFoundException:An Authentication object was not found in the SecurityContext
  alice 调 outer()(类内部 this.admin()) ⇒ outer 里调 this.admin() → admin 的数据
  alice 调 pkgPrivate()(同包,package-private) ⇒ 抛 AuthorizationDeniedException:Access Denied
  alice 调 callPriv()(public 方法里调 private 的 priv()) ⇒ private 方法执行了
  alice 调 viaIface()(注解写在接口上) ⇒ 抛 AuthorizationDeniedException:Access Denied

CGLIB 代理;自调用、private 方法绕过;接口上的注解有效。

表达式

实测 · 本机执行输出
  alice 调 mine("alice") ⇒ 只能看自己的:alice
  alice 调 mine("bob") ⇒ 抛 AuthorizationDeniedException:Access Denied
  alice 调 doc("alice")(@PostAuthorize) ⇒ Doc[owner=alice, text=内容]
  alice 调 doc("bob")(方法已经执行,返回值被拒绝) ⇒ 抛 AuthorizationDeniedException:Access Denied
  alice 调 count(三个文档,两个属于 bob)(@PreFilter) ⇒ 1
  alice 调 all()(@PostFilter) ⇒ [Doc[owner=alice, text=a1], Doc[owner=alice, text=a2]]
  alice 调 count(不可变列表 List.of)(@PreFilter 要原地删元素) ⇒ 2

参数名、返回值、集合过滤;@PostAuthorize 方法体先执行。

FACTOR_PASSWORD

实测 · 本机执行输出
  手工构造的 Authentication 调 factor()(authorities 只有 ROLE_USER) ⇒ 抛 AuthorizationDeniedException:Access Denied

Spring Security 7 认证后多出的权限,手工构造的 Authentication 要自己带上。

注解开关

实测 · 本机执行输出
  alice 调 secured() (默认的 @EnableMethodSecurity) ⇒ @Secured 方法执行了
  alice 调 rolesAllowed() (默认的 @EnableMethodSecurity) ⇒ @RolesAllowed 方法执行了
  root 调 secured() (默认的 @EnableMethodSecurity) ⇒ @Secured 方法执行了
  alice 调 secured() (@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)) ⇒ 抛 AuthorizationDeniedException:Access Denied
  alice 调 rolesAllowed() (@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)) ⇒ 抛 AuthorizationDeniedException:Access Denied
  root 调 secured() (@EnableMethodSecurity(securedEnabled = true, jsr250Enabled = true)) ⇒ @Secured 方法执行了

@Secured、@RolesAllowed 默认不生效,要显式打开。

分步演示

方法级安全
本步的对象

    本文用到的工具与边界

    • 本机 macOS 27 arm64 / OpenJDK 21.0.12(Homebrew) / Spring Boot 4.0.8(Spring Framework 7.0.9、Tomcat 11.0.24) / Spring Cloud 2025.1.3;用 javac 编译、java 直接运行,依赖由 Maven 从 Maven Central 解析;数字每次运行会有小幅抖动(Spring Security 7,spring-boot-starter-security)。非 Web 模式的单个程序;当前用户由代码直接放进 SecurityContextHolder(UsernamePasswordAuthenticationToken.authenticated);「默认」与「securedEnabled / jsr250Enabled」是启动同一个程序两次(-Dx108.extra)
    • 没有验证:@Transactional 与 @PreAuthorize 的执行顺序;AspectJ 模式(@EnableMethodSecurity(mode = ASPECTJ))能否处理自调用;自定义 AuthorizationManager(见第 109 篇);@PreAuthorize 放在 Controller 方法上;@HandleAuthorizationDenied 的自定义处理;元注解;响应式方法安全;@PreFilter 对不可变集合的具体失败原因(只观察到结果)
    京ICP备2026067502号-1京公网安备11010502063487号