问题背景
最近接手了一套基于 RuoYi 框架的微服务系统,架构如下:
- 后端 A:负责认证,生成 Token。
- 后端 B:纯业务服务,按需求所有接口均不需鉴权。
- 前端 :请求时会统一携带后端 A 生成的
AuthorizationHeader。
在维护后端 B 时,我发现了一段前任留下的代码:
java
// 后端 B 的 Security 配置
.antMatchers("/**").anonymous()
这段代码显然是有问题的。因为前端请求一定会带 Token,触发认证后用户状态会变成"已认证",而 anonymous() 规则限制"只允许匿名访问",两者冲突必然导致 403 Forbidden。
诡异的现象
我也确实遇到了 403 问题。但令人费解的是,同样的代码、同样的配置 ,在另一台服务器上部署运行时,接口竟然是正常 的(返回 200)。
这就很奇怪了:
- 如果说配置错了,那应该两边都 403。
- 为什么一边 403,一边却能正常跑通?
深入源码与逻辑推演
既然代码层面没差异,问题一定出在运行时数据上。
我们知道 RuoYi 框架的 JwtAuthenticationTokenFilter 处理逻辑分为两步:
- 解析 Token :使用
secret解析 JWT 签名。 - 校验状态 :根据 Token 中的 ID 去 Redis 查询用户详细信息(
login_tokens:xxx)。
此时,用户的认证状态取决于这两个步骤的结果:
- 如果解析成功且 Redis 有数据 -> 已认证用户。
- 如果解析失败或 Redis 无数据 -> 匿名用户 。
结合anonymous()的规则(只允许匿名用户),我突然意识到:那台"正常"的服务器,是不是因为某种原因,导致认证失败了?
真相大白:Redis 配置差异
带着这个假设,我对比了两台服务器的配置文件(application.yml),终于发现了端倪:
1. 报 403 的服务器(共享 Redis)
- 配置的
secret与后端 A 一致。 - 配置的 Redis 地址与后端 A 一致(共享了 Redis 库)。
- 结果 :Token 解析成功 -> Redis 中查到了用户数据 -> 认证成功(用户变为已认证) -> 撞上
anonymous()规则 -> 403 。
2. "正常"的服务器(独立 Redis) - 配置的
secret与后端 A 一致。 - 配置的 Redis 地址是连接了不同的库,里面没有后端 A 生成的用户缓存。
- 结果 :Token 解析成功 -> 去 Redis 查用户信息查无数据 -> 抛出异常/认证失败 -> 用户状态保持为匿名 -> 符合
anonymous()规则 -> 200 正常。 - (这是一次"负负得正"!因为 Redis 配置隔离导致认证失败,反而误打误撞绕过了错误的
anonymous()限制。)
解决方案与反思
原因找到了,解决起来就很简单了。
1. 修正配置
既然业务需求是"接口无需鉴权",那么必须将 anonymous() 修改为 permitAll(),这才是语义正确的配置:
java
.antMatchers("/**").permitAll()
2. 思考
这次排查给我最大的启示是:环境差异往往会掩盖代码本身的错