shiro或spring security对匿名用户请求缓存本身无害,问题在于缓存策略失控:未设过期时间、无容量上限、key设计不合理,导致对象长期驻留堆内存,引发oom。

Shiro 或 Spring Security 对匿名用户请求做缓存,本身不是问题;问题出在缓存策略失控——对象长期驻留、未设上限、未及时淘汰,最终拖垮堆内存。
缓存对象没设过期或容量限制
框架默认可能启用缓存(如 Shiro 的 AuthorizationInfo 缓存、Spring Security 的 DefaultCacheBasedRequestMatcher),但若未配置 TTL 或最大条目数,每次匿名请求都会生成新缓存项并永久保留。
- 例如:匿名访问 `/public/**` 路径时,Security 框架仍会构造并缓存 `Authentication` 或 `FilterInvocation` 对象
- 这些对象常持有 `Principal`、`GrantedAuthority`、甚至 `HttpServletRequest` 引用,间接拉入大量临时数据
- 静态 Map 或未配置回收策略的 Guava/Caffeine 缓存,会持续增长,GC 无法清理
RememberMe 或 Session 相关缓存滥用
Shiro 的 `CookieRememberMeManager` 或 Spring Session + Redis 组合,在处理未登录用户时,也可能误发或冗余创建 session/remember-me 上下文。
- 比如每次匿名请求都触发 `createSession()`,即使只存空 session ID,Redis 中也会堆积大量过期键(
spring:session:sessions:expires:{id}) - Shiro 默认使用 `MemorySessionDAO` 时,session 实例直接留在堆中,无自动清理机制
- 结合 `ThreadLocal` 存储上下文(如 `SecurityUtils.getSubject()` 返回的 Subject),线程复用场景下易引发隐式内存滞留
缓存 Key 设计不合理,导致缓存爆炸
若缓存 Key 包含动态参数(如时间戳、随机 UUID、完整 request URI),则几乎每个请求都命中新 Key,缓存彻底失效且持续膨胀。
- 典型反例:
cache.put(request.getRequestURL() + "?" + request.getQueryString(), authResult) - URI 中含分页参数、traceId、时间戳等,导致缓存项不可复用,数量随 QPS 线性增长
- Key 未标准化(大小写、编码、顺序差异),进一步加剧重复缓存
解决方案:轻量、可控、可监控
不靠禁用安全功能,而靠精准控制缓存生命周期:
- 显式关闭非必要缓存:Shiro 中设
securityManager.cacheManager = null;Spring Security 中禁用requestCache或使用空实现 - 对必须缓存的授权结果,统一用 Caffeine 并强制配置:
maximumSize(1000).expireAfterWrite(10, TimeUnit.MINUTES) - 匿名路径走白名单直通,绕过所有认证/授权缓存逻辑(如 Spring Security 的
permitAll()配置需前置且明确) - 开启 Actuator 的
/actuator/metrics/cache.*端点,监控 hit/miss ratio 和 size,异常增长即告警
Java免费学习笔记:立即使用
解锁 Java 大师之旅:从入门到精通的终极指南











