WildFly环境下JWT跨线程黑名单实现及空指针问题排查
JWT黑名单实现问题排查与优化建议
一、synchronized处空指针异常的解决
问题根源
触发空指针的直接原因是blackList变量为null,导致进入synchronized(blackList)代码块时抛出异常。
可能的触发场景
- 类未被Spring正确管理:如果代码中存在手动
new JwtTokenUtil()的情况,而非通过Spring容器注入(如@Autowired),极端场景下(如反射实例化)可能导致blackList的初始化逻辑未执行,或者后续被其他代码将blackList置为null。 - 变量被意外修改:检查类中是否存在其他方法主动将
blackList赋值为null。
修复方案
- 强制Spring容器管理实例:确保所有使用
JwtTokenUtil的地方都通过Spring注入获取实例,禁止手动创建对象。 - 锁定变量引用:将
blackList声明为final,避免被意外赋值为null:private final List<String> blackList = new ArrayList<>(); - 显式构造初始化:如果担心实例变量初始化逻辑失效,可在构造方法中显式初始化:
private List<String> blackList; public JwtTokenUtil() { this.blackList = new ArrayList<>(); }
二、当前实现的合理性分析
当前用ArrayList实现黑名单的方式完全不符合WildFly多线程、多用户的需求,存在以下核心问题:
- 线程安全隐患:虽然用了
synchronized块,但ArrayList本身是非线程安全集合,并发场景下仍可能出现遍历、修改的一致性问题。 - 内存泄漏风险:仅在验证和刷新时过滤过期令牌,随着用户量增长,黑名单会持续膨胀,最终耗尽JVM内存。
- 性能低下:每次验证都要遍历整个黑名单过滤过期令牌,还要通过Stream匹配当前令牌,黑名单规模较大时,验证性能会急剧下降。
- 集群场景失效:如果WildFly是集群部署,每个节点的黑名单都是独立的,已拉黑的令牌在其他节点仍能正常使用,完全达不到全局生效的要求。
三、优化后的实现方案
针对WildFly的多线程、跨节点需求,推荐以下优化方案:
1. 替换为线程安全的过期集合
使用ConcurrentHashMap存储令牌与过期时间,替代ArrayList,既保证线程安全,又能高效判断令牌是否过期:
@Component public class JwtTokenUtil { // key: JWT令牌, value: 令牌过期时间戳 private final ConcurrentHashMap<String, Long> blackList = new ConcurrentHashMap<>(); // 获取令牌过期时间的辅助方法 private Date getExpirationDateFromToken(String token) { Claims claims = getClaimsFromToken(token); return claims.getExpiration(); } // 将令牌加入黑名单 public void addToBlackList(String token) { long expireTime = getExpirationDateFromToken(token).getTime(); blackList.put(token, expireTime); } // 检查令牌是否在黑名单中且未过期 public boolean isTokenBlacklisted(String token) { Long expireTime = blackList.get(token); if (expireTime == null) { return false; } // 令牌已过期则移除并返回未拉黑 if (System.currentTimeMillis() > expireTime) { blackList.remove(token); return false; } return true; } }
2. 定时清理过期令牌
添加定时任务定期清理过期令牌,避免内存占用过高:
@Scheduled(fixedRate = 3600000) // 每小时执行一次清理 public void cleanExpiredTokens() { long currentTime = System.currentTimeMillis(); blackList.entrySet().removeIf(entry -> entry.getValue() < currentTime); }
3. 集群场景下的全局黑名单
如果WildFly是集群部署,必须使用分布式存储实现全局黑名单:
- Redis方案:将令牌作为Redis的Key,过期时间设置为JWT的剩余有效期,利用Redis的自动过期特性,无需手动清理,且天然支持跨节点共享。
- WildFly内置缓存:使用WildFly自带的Infinispan分布式缓存,配置集群同步的缓存实例存储黑名单。
4. 修正刷新令牌方法
修复空指针并优化逻辑,确保刷新时旧令牌被正确加入黑名单:
public Boolean canTokenBeRefreshed(String token) { if (token == null || isTokenExpired(token)) { return false; } // 将旧令牌加入黑名单 addToBlackList(token); return true; }
5. 优化令牌验证逻辑
更新getUsernameFromToken方法,使用新的黑名单检查逻辑:
public String getUsernameFromToken(String token) { String username = null; if (token != null) { if (isTokenBlacklisted(token)) { throw new RuntimeException("JWT is invalid"); } final Claims claims = getClaimsFromToken(token); username = claims.getSubject(); } // 后续逻辑... return username; }
内容的提问来源于stack exchange,提问作者Alex
相关产品推荐
相关产品推荐

