🚨“为什么我的接口突然 403 了?”
作者:旷野说
关键词:Spring Security、Servlet Filter、403 异常、安全架构、过滤器链、口诀记忆
🧩 一、那个让人抓狂的下午
上周三下午,我正和前端同事联调一个新功能。
他调用 /api/v1/orders/create 接口,返回:
{
"status": 403,
"message": "Access Denied"
}
“你是不是没给我开权限?”他问。
我皱眉:“不可能。我用的是管理员账号,Token 是刚登录拿的,而且本地跑得好好的。”
我打开 Postman 重试:
- ✅
/login→ 成功,返回 JWT; - ✅
/api/v1/profile→ 成功,返回用户信息; - ❌
/api/v1/orders/create→ 403,Access Denied。
更诡异的是:断点根本没进 Controller。
我甚至在 Controller 第一行打了日志,结果日志没输出。
“请求……根本没进来?”我喃喃自语。
那一刻,我意识到:
我的代码不是被业务逻辑拒绝的,而是被某一层“看不见的安全机制”提前拦截了。
而我对这层机制,几乎一无所知。
❓ 二、带着问题,向底层进发
我开始追问自己:
-
请求到底是在哪被拦下的?
是 Spring 的 AOP?还是更早的某个地方? -
为什么有些接口能通,有些不行?
它们都在同一个@RestController里啊! -
Spring Security 的
authorizeHttpRequests()和@PreAuthorize到底谁先生效? -
我的 JWT 解析 Filter,真的在权限校验前执行了吗?
这些问题,像一张密网,把我困在“配置能跑,但原理不清”的模糊地带。
于是,我决定:不再把 Spring Security 当成黑盒。我要搞清楚,从请求进来到被拦截,究竟发生了什么。
🔍 三、真相的起点:Servlet Filter
经过翻源码、读文档、画流程图,我终于找到了答案的起点——
Spring Security 的安全拦截,根本不在 Spring 的 Bean 层,而是在更底层:Servlet 容器层。
换句话说:
你的请求,甚至还没到达 DispatcherServlet,就已经被一连串 Filter 检查过了。
而这一切,都基于 Java Web 最基础的机制:Servlet Filter。
📦 四、什么是 Servlet Filter?
Filter 是 Java EE(现 Jakarta EE)标准的一部分,由 Tomcat、Jetty 等容器管理。
它能在 请求到达目标资源(如 Controller)前,或 响应返回前,对请求/响应进行拦截和处理。
核心方法就一个:
void doFilter(ServletRequest req, ServletResponse res, FilterChain chain)
- 调用
chain.doFilter(...)→ 放行,继续执行; - 不调用 → 拦截,直接返回(比如 403)。
⚠️ Filter 是链式执行的,顺序至关重要。
🧱 五、Spring Security 的核心:FilterChainProxy
Spring Security 并没有把十几个安全 Filter 直接注册到 Tomcat,而是把它们全部封装在一个特殊的 Filter 里:FilterChainProxy。
它像一个“安全中枢”,内部维护了一整条过滤器链:
graph LR
A[HTTP Request] --> B[FilterChainProxy]
B --> C[SecurityContextPersistenceFilter]
B --> D[JwtAuthenticationFilter] // 我们自定义的
B --> E[ExceptionTranslationFilter]
B --> F[FilterSecurityInterceptor] // 最终权限校验!
F --> G[DispatcherServlet → Controller]
💡 关键发现:
我的/orders/create接口之所以 403,就是因为FilterSecurityInterceptor拦截了它——
而这时,我的 Controller 根本还没机会执行!
🔑 六、为什么 /profile 能过,/orders/create 不行?
我检查了安全配置:
.authorizeHttpRequests(authz -> authz
.requestMatchers("/login").permitAll()
.anyRequest().authenticated()
)
看起来没问题啊?所有接口只要登录就能访问。
但后来我发现:我在 Service 层还加了 @PreAuthorize!
@Service
public class OrderService {
@PreAuthorize("hasRole('ADMIN')")
public Order createOrder(OrderDto dto) { ... }
}
而 /profile 没有方法级注解。
✅ 真相大白:
/profile:只经过 Filter 层(URL 权限),通过;/orders/create:先过 Filter 层(通过),再进 AOP 层(方法权限),因无 ADMIN 角色被拒!
但为什么没看到异常堆栈?
因为 ExceptionTranslationFilter 把 AccessDeniedException 捕获后,静默转成了 403 响应——这正是 Spring Security 的“优雅”设计,也是调试的难点。
⚖️ 七、Filter vs AOP:职责分明
现在我终于理清了两者的分工:
| 机制 | 作用层级 | 典型用途 | 是否依赖 Spring |
|---|---|---|---|
| Servlet Filter | HTTP 请求层 | JWT 认证、CORS、日志、URL 权限 | ❌ 不依赖(容器级) |
| Spring AOP | 方法调用层 | @PreAuthorize、事务、缓存 | ✅ 依赖 Spring Bean |
✅ Spring Security = Filter(Web 安全) + AOP(方法安全)
- Filter 拦请求:决定“你能不能访问这个 URL”;
- AOP 拦方法:决定“你能不能调用这个方法”。
🧠 八、终极口诀:14 个字,牢牢记住
“Filter 拦请求,AOP 拦方法”
“Web 层用 Filter,服务层用 AOP”
- 遇到 401/403 且 Controller 没执行? → 查 Filter 链(尤其是
FilterSecurityInterceptor)。 - 遇到 方法被拒但 URL 能通? → 查
@PreAuthorize和角色权限。
✅ 九、总结:从“能用”到“懂用”
这次 403 异常,像一记警钟,敲醒了我:
不要把框架当魔法。
只有理解底层机制,才能在问题发生时,一眼看穿本质。
现在,当我再看到 Spring Security 的配置:
.addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
.sessionManagement().sessionCreationPolicy(STATELESS)
.authorizeHttpRequests(...).authenticated()
我不再觉得它只是“模板代码”,而是一个精密的安全流水线——每一步都有其不可替代的作用。
而那个下午的困惑,也成了我深入理解 Web 安全架构的起点。
📚 延伸建议
- 调试技巧:在
FilterSecurityInterceptor和MethodSecurityInterceptor中打断点; - 日志增强:开启
logging.level.org.springframework.security=DEBUG; - 安全测试:使用 Postman + 不同角色 Token 验证权限边界。
愿你下次再遇 403,不再慌张,而是微微一笑:
“哦,我知道你在哪拦我了。”
揭秘Spring Security拦截机制
1699

被折叠的 条评论
为什么被折叠?



