我正在使用JWT在我的Spring Boot应用程序中进行身份验证.以下是身份验证服务中的方法:

public AuthenticationResponse authenticate(AuthenticationRequest request) {
        authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(
                        request.getEmail(),
                        request.getPassword()
                )
        );
        User user = userRepository.findByEmail(request.getEmail()).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();
    }

private void revokeAllUserTokens(User user) {
        List<Token> validUserTokens = tokenRepository.findAllValidTokenByUser(user.getId());
        if(validUserTokens.isEmpty())
            return;
        validUserTokens.forEach(token -> {
            token.setExpired(true);
            token.setRevoked(true);
        });
        tokenRepository.saveAll(validUserTokens);
    }

private void saveUserToken(User user, String jwtToken) {
        Token token = Token.builder()
                .user(user)
                .token(jwtToken)
                .tokenType(TokenType.BEARER)
                .expired(false)
                .revoked(false)
                .build();
        tokenRepository.save(token);
    }

在JwtService中:

public String generateToken(UserDetails userDetails) {
        return generateToken(new HashMap<>(), userDetails);
    }

    public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
        return buildToken(extraClaims, userDetails, jwtConfig.getExpiration());
    }

    public String generateRefreshToken(UserDetails userDetails) {
        return buildToken(new HashMap<>(), userDetails, jwtConfig.getRefreshTokenExpiration());
    }

和令牌在数据库中:

@Data
@Builder
@NoArgsConstructor
@AllArgsConstructor
@Entity
@Table(name = "jwt_tokens")
public class Token {

    @Id
    @GeneratedValue
    public Long id;

    @Column(name = "token", unique = true)
    public String token;

    @Enumerated(EnumType.STRING)
    public TokenType tokenType = TokenType.BEARER;

    public boolean revoked;
    public boolean expired;

    @ManyToOne(fetch = FetchType.LAZY)
    @JoinColumn(name = "user_id")
    public User user;

}

实现的其余部分是不言而喻的.

它工作得很好,但当有多个身份验证请求时(当我快速发送多个请求时),会有500个状态响应,并抛出一个异常(AuthenticationService中的方法Authate)被调用:

org.h2.jdbc.JdbcSQLIntegrityConstraintViolationException: Unique index or primary key violation: "PUBLIC.CONSTRAINT_INDEX_4 ON PUBLIC.JWT_TOKENS(TOKEN NULLS FIRST) VALUES ( /* 7 */ 'eyJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJtYWx5enNAZW1haWwuY29tIiwiaWF0IjoxNjkwMjE4NDU3LCJleHAiOjE2OTAzMDQ4NTd9.8aePz6jl3JPvsnDFVpXxtzYfsxLN0ZqIUg0FsK_XDaE' )"; SQL statement:
insert into jwt_tokens (expired,revoked,token,token_type,user_id,id) values (?,?,?,?,?,?) [23505-214]

推荐答案

根据设计,JWT令牌对iat(颁发于)时间使用以秒为单位的UNIX纪元时间.

我认为一个好办法是在生成JWT令牌时在extraClaims中包含额外的信息,例如当前时间(以毫秒为单位).这不会改变令牌的使用,因为它们仍然会被散列.

就您的实施而言,可能的更改是:

String jwtToken = jwtService.generateToken(Map.of("milis", new Date(System.currentTimeMillis())), user);
String refreshToken = jwtService.generateRefreshToken(Map.of("milis", new Date(System.currentTimeMillis())), user);

Java相关问答推荐

Java SSLocket查明客户端是否发送了证书

如何用javac编译Java类,即使对像java.lang.*这样的基本类也没有任何依赖关系?

Mongo DB Bson和Java:在子文档中添加和返回仅存在于父文档中的字段?

如何在Android上获取来电信息

scanner 如何在执行hasNextLine一次后重新读取整个文件?

确定Java中Math.Ranb()输出的上限

如何调整工作时间日历中的时间

从Spring5迁移到Spring6:无法在雅加达包中找到类

计算两个浮点数之间的距离是否对称?

GSON期间的Java类型擦除

Spring Security不允许加载js

Sack()步骤中的合并运算符未按预期工作

如何集成语义发布和BitBucket(Java项目)

IntelliJ IDEA依赖项工具窗口丢失

如何将RESTAssured';S的Http标题转换为<;字符串、字符串和>的映射?

Intellij 2023 IDE:始终在顶部显示菜单栏

始终使用Spring Boot连接mongodb上的测试数据库

人们在 IntelliJ 上将-Dhttp.proxyHost=your.proxy.net -Dhttp.proxyPort=8080放在哪里?

声纳覆盖范围为 0%,未生成 jacoco.xml

JAVA 正则表达式识别字符串string或字符串内的字符char