@PreAuthorize、JSON to Object、Bean Validation的执行顺序
2026-03-09
执行顺序通常是:JSON 反序列化 -> 属性校验 (@Valid) -> @PreAuthorize 权限检查 -> 业务方法执行。
这意味着:
先把请求的 JSON 数据转换成 Java 对象。
然后检查这个对象里的数据是否符合规则(如非空、长度限制等)。
最后才检查当前用户是否有权限执行该方法(
@PreAuthorize)。
如果 JSON 格式错误或者属性校验失败,@PreAuthorize 根本不会执行。
详细执行流程解析
这个顺序是由 Spring MVC 和 Spring Security 的拦截器链(Interceptor Chain)及 AOP 代理机制决定的。以下是基于 Spring Boot (Spring MVC + Spring Security) 的标准请求处理流程:
1. 请求进入 DispatcherServlet
所有 HTTP 请求首先到达 DispatcherServlet。
2. 参数解析与 JSON 反序列化 (HttpMessageConverter)
阶段:在调用 Controller 方法之前,Spring MVC 需要处理方法参数(例如
@RequestBody User user)。动作:
RequestResponseBodyMethodProcessor会使用HttpMessageConverter(如 Jackson)将 HTTP 请求体中的 JSON 字符串转换为 Java 对象。结果:此时 Java 对象已经创建完成。如果 JSON 格式错误(如类型不匹配),这里直接抛出
HttpMessageNotReadableException,流程终止。
3. 参数校验 (Bean Validation / @Valid)
阶段:参数解析完成后,方法调用前。
动作:如果参数上有
@Valid、@Validated或字段上有校验注解,MethodValidationPostProcessor(或 Spring MVC 内置的校验逻辑) 会立即执行校验。结果:如果校验失败(例如必填项为空),抛出
MethodArgumentNotValidException或ConstraintViolationException,流程终止。此时尚未进入 Spring Security 的方法拦截器。
4. Spring Security 拦截器 (MethodSecurityInterceptor)
阶段:参数已准备好且校验通过,准备真正调用目标 Bean 的方法时。
动作:
Spring Security 通过 AOP(动态代理)包裹了你的 Service 或 Controller Bean。
当代码试图调用目标方法时,首先被代理对象拦截。
MethodSecurityInterceptor获取@PreAuthorize注解。它计算 SpEL 表达式(例如
hasRole('ADMIN')或#user.id == authentication.principal.id)。注意:此时因为第 1 步已完成,SpEL 表达式中可以安全地引用方法参数(如#user)。
结果:
如果权限不足,抛出
AccessDeniedException。如果权限通过,才允许执行真正的目标方法。
5. 业务方法执行
只有上述所有步骤都成功,你的业务代码才会运行。
为什么是这个顺序?
数据依赖:
@PreAuthorize的 SpEL 表达式经常需要使用方法参数作为判断依据(例如:@PreAuthorize("#userId == authentication.principal.id")或@PreAuthorize("@service.check(#user)"))。如果不先进行 JSON 反序列化和校验,表达式中引用的参数可能不存在或不合法,导致无法进行权限判断。职责分离:
MVC 层 负责处理 HTTP 协议、数据绑定和数据格式的合法性(是不是个合法的对象?)。
Security 层 负责业务逻辑入口的安全性(这个人能不能做这件事?)。
Service 层 负责具体的业务逻辑。
通常认为,如果数据本身都是错的(校验失败),讨论“谁有权限操作这些错误数据”是没有意义的,因此校验优先于权限检查。
场景 A:发送非法 JSON (如 age: "abc")
JSON 反序列化失败。
抛出异常。
@PreAuthorize 未执行。
场景 B:发送 JSON 但校验失败 (如 name: null,而 UserDto 中 name 有 @NotNull)
JSON 反序列化成功,生成对象。
@Valid触发校验,发现 name 为空。抛出
MethodArgumentNotValidException。@PreAuthorize 未执行。
场景 C:发送合法 JSON,但用户角色不对
JSON 反序列化成功。
@Valid校验通过。进入 Spring Security 代理。
@PreAuthorize检查角色,发现不是 ADMIN。抛出
AccessDeniedException(403 Forbidden)。
场景 D:发送合法 JSON,且用户角色正确
JSON 反序列化成功。
@Valid校验通过。@PreAuthorize检查通过。执行方法体。
特殊情况说明
虽然标准顺序如上,但在极少数自定义配置下可能会改变:
自定义 ArgumentResolver:如果你完全自定义了参数解析器,并且在该解析器内部手动调用了安全逻辑,顺序可能改变(极不推荐)。
Filter 级别的权限控制:如果你在
SecurityFilterChain中配置了基于 URL 的权限(如.requestMatchers("/admin/**").hasRole("ADMIN")),那么这个 Filter 会在 JSON 反序列化之前 就执行。但这是针对 URL 路径的粗粒度控制,不是@PreAuthorize注解的行为。@PreAuthorize特指方法级安全,必然在参数解析之后。
总结
对于 @PreAuthorize 注解:
JSON 转 Java 对象:先执行。
属性校验 (
@Valid):先执行。@PreAuthorize:后执行。
这种设计确保了你在编写权限表达式(SpEL)时,可以放心地使用已经过校验的方法参数。