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

Spring Boot JWT认证并发请求触发唯一索引冲突问题

问题分析与解决方案

问题根源

  1. JWT生成重复:你的buildToken方法使用毫秒级时间戳作为签发时间(iat),高并发场景下同一毫秒内的请求会生成完全相同的JWT——因为claims、签名密钥、过期时间都一致,最终生成的token字符串完全重复。
  2. 并发操作非原子:认证流程中,revokeAllUserTokens和saveUserToken并非原子操作,多个请求会同时生成重复token,绕过revoke步骤后同时插入数据库,触发token字段的唯一索引约束。

具体修复方案

1. 确保JWT的唯一性

在生成JWT时加入唯一标识,避免同一用户同一时间生成重复token:

// JwtService.java
public String generateToken(UserDetails userDetails) {
    Map<String, Object> extraClaims = new HashMap<>();
    extraClaims.put("jti", UUID.randomUUID().toString()); // 添加唯一ID作为额外Claim
    return generateToken(extraClaims, userDetails);
}

或者改用纳秒级时间戳提高时间精度:

// JwtService.java 中buildToken方法修改签发时间
private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
    return Jwts.builder()
            .setClaims(extraClaims)
            .setSubject(userDetails.getUsername())
            .setIssuedAt(new Date(System.nanoTime())) // 纳秒级时间戳替代毫秒
            .setExpiration(new Date(System.nanoTime() + expiration))
            .signWith(getSignInKey(), SignatureAlgorithm.HS256)
            .compact();
}

2. 保证认证操作的原子性

用事务+行锁防止同一用户的并发认证请求冲突:

// AuthenticationService.java
@Transactional // 确保revoke和save操作在同一事务中
public AuthenticationResponse authenticate(AuthenticationRequest request) {
    authenticationManager.authenticate(
            new UsernamePasswordAuthenticationToken(
                    request.getEmail(),
                    request.getPassword()
            )
    );
    // 查询用户时加悲观写锁,阻止其他线程同时修改该用户的token记录
    User user = userRepository.findById(
            userRepository.findByEmail(request.getEmail())
                    .orElseThrow(UserNotFoundException::new)
                    .getId()
    ).lock().orElseThrow(UserNotFoundException::new);

    String jwtToken = jwtService.generateToken(user);
    String refreshToken = jwtService.generateRefreshToken(user);

    revokeAllUserTokens(user);
    saveUserToken(user, jwtToken);

    return AuthenticationResponse.builder()
            .accessToken(jwtToken)
            .refreshToken(refreshToken)
            .build();
}

同时给UserRepository添加锁支持:

// UserRepository.java
@Lock(LockModeType.PESSIMISTIC_WRITE)
Optional<User> findById(Long id);

3. 优化Token吊销逻辑

用批量更新替代"查询-修改-保存"的低效流程,减少并发冲突概率:

// AuthenticationService.java 中修改revoke方法
private void revokeAllUserTokens(User user) {
    tokenRepository.markAllValidTokensAsRevoked(user.getId());
}

// TokenRepository.java 添加JPQL语句
@Modifying
@Query("UPDATE Token t SET t.revoked = true, t.expired = true WHERE t.user.id = :userId AND t.revoked = false AND t.expired = false")
void markAllValidTokensAsRevoked(@Param("userId") Long userId);

内容的提问来源于stack exchange,提问作者Mixss

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.15 06:35:54