Spring Security:使用PersistentToken时移动端频繁触发CookieTheftException问题
我之前也碰到过几乎一模一样的情况——一堆CookieTheftException日志刷得服务器告警,结果排查下来根本不是真的Cookie被盗,就是配置或者移动端浏览器的特性搞的鬼。结合你说的只在移动端出现的情况,给你几个排查方向和解决办法:
1. 先查数据库令牌表的字段长度
这是最容易踩的坑!Spring Security的PersistentTokenRepository默认用的persistent_logins表,其中series和token_value字段需要足够的长度来存储生成的令牌字符串。如果你的表是自己手动创建的,很可能字段长度设短了,导致存储的令牌被截断,移动端浏览器发过来完整的Cookie时,服务器比对就会触发异常。
先跑个SQL看看表结构:
DESCRIBE persistent_logins;
要是series或token_value的长度小于64(保险起见设64位),赶紧修改表结构:
ALTER TABLE persistent_logins MODIFY COLUMN series VARCHAR(64) NOT NULL; ALTER TABLE persistent_logins MODIFY COLUMN token_value VARCHAR(64) NOT NULL;
2. 调整记住我令牌的刷新策略
默认情况下,PersistentTokenBasedRememberMeServices每次请求都会刷新令牌——更新数据库里的token_value,同时发新的Cookie给客户端。但移动端浏览器有时候会有缓存、会话恢复的特性,可能同时发送旧Cookie和新Cookie,服务器就会误以为是令牌被盗。
你可以改成只在登录时生成令牌,后续请求不刷新,同时设置一个合理的有效期:
@Bean public RememberMeServices rememberMeServices(UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) { PersistentTokenBasedRememberMeServices services = new PersistentTokenBasedRememberMeServices( "your-remember-me-secret-key", userDetailsService, tokenRepository); services.setAlwaysRemember(false); // 禁用每次请求刷新令牌,设置有效期为7天 services.setTokenValiditySeconds(86400 * 7); return services; }
这样能大幅减少令牌更新的频率,降低新旧Cookie冲突的概率。
3. 检查Cookie的路径和域配置
如果你的Web应用部署在特定的上下文路径下,或者移动端访问的域名有子域名差异,可能导致Cookie的路径/域不匹配,服务器收到的Cookie和预期的不一致,也会触发异常。
在Spring Security配置里明确指定Cookie的路径和域:
@Override protected void configure(HttpSecurity http) throws Exception { http.rememberMe() .rememberMeServices(rememberMeServices()) .cookieName("my-remember-me-cookie") .path("/") // 设为根路径,确保所有请求都能携带 .domain("your-app-domain.com"); // 换成你的实际域名,注意子域名的情况 }
4. 排查User-Agent相关的自定义校验
如果你自己扩展了RememberMeServices,加入了User-Agent的校验逻辑,那可要注意了——移动端浏览器经常会切换桌面/移动模式,导致User-Agent变化,这时候校验失败就会抛出CookieTheftException。
要是是这种情况,要么移除User-Agent的校验,要么放宽规则(比如只校验浏览器主版本号)。
5. 自定义异常处理过滤误报
如果前面的方法都没解决,你可以自己写个RememberMeServices的子类,捕获CookieTheftException后判断是否是移动端请求,只记录警告而不是抛出异常:
public class MobileFriendlyRememberMeServices extends PersistentTokenBasedRememberMeServices { public MobileFriendlyRememberMeServices(String key, UserDetailsService userDetailsService, PersistentTokenRepository tokenRepository) { super(key, userDetailsService, tokenRepository); } @Override protected void onCookieTheft(HttpServletRequest request, HttpServletResponse response) { String userAgent = request.getHeader("User-Agent"); // 判断是否是移动端请求(这里的判断逻辑可以根据实际情况调整) if (userAgent != null && (userAgent.contains("Mobile") || userAgent.contains("Android") || userAgent.contains("iPhone"))) { logger.warn("移动端疑似Cookie误报,User-Agent: {}", userAgent); return; // 不触发后续的令牌失效逻辑 } // 非移动端按原逻辑处理,比如失效令牌、通知用户 super.onCookieTheft(request, response); } }
然后在配置里替换成这个自定义的服务就行。
内容的提问来源于stack exchange,提问作者Rüdiger Schulz

