You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

WildFly环境下JWT跨线程黑名单实现及空指针问题排查

JWT黑名单实现问题排查与优化建议

一、synchronized处空指针异常的解决

问题根源

触发空指针的直接原因是blackList变量为null,导致进入synchronized(blackList)代码块时抛出异常。

可能的触发场景

  1. 类未被Spring正确管理:如果代码中存在手动new JwtTokenUtil()的情况,而非通过Spring容器注入(如@Autowired),极端场景下(如反射实例化)可能导致blackList的初始化逻辑未执行,或者后续被其他代码将blackList置为null。
  2. 变量被意外修改:检查类中是否存在其他方法主动将blackList赋值为null。

修复方案

  1. 强制Spring容器管理实例:确保所有使用JwtTokenUtil的地方都通过Spring注入获取实例,禁止手动创建对象。
  2. 锁定变量引用:将blackList声明为final,避免被意外赋值为null:
    private final List<String> blackList = new ArrayList<>();
    
  3. 显式构造初始化:如果担心实例变量初始化逻辑失效,可在构造方法中显式初始化:
    private List<String> blackList;
    public JwtTokenUtil() {
        this.blackList = new ArrayList<>();
    }
    

二、当前实现的合理性分析

当前用ArrayList实现黑名单的方式完全不符合WildFly多线程、多用户的需求,存在以下核心问题:

  1. 线程安全隐患:虽然用了synchronized块,但ArrayList本身是非线程安全集合,并发场景下仍可能出现遍历、修改的一致性问题。
  2. 内存泄漏风险:仅在验证和刷新时过滤过期令牌,随着用户量增长,黑名单会持续膨胀,最终耗尽JVM内存。
  3. 性能低下:每次验证都要遍历整个黑名单过滤过期令牌,还要通过Stream匹配当前令牌,黑名单规模较大时,验证性能会急剧下降。
  4. 集群场景失效:如果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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.07.23 15:14:58