jwtk/jjwt

JwtParser.parseClaimsJws doesn't consider token just created by JwtBuilder.compact to be valid

Joshua-Schroijen opened this issue · 2 comments

Describe the bug
JwtParser.parseClaimsJws doesn't consider my token just created by JwtBuilder.compact to be valid. The exact same javax.crypto.SecretKey is used for signing and checking. The token is definitely not expired. When JwtParser.parseClaimsJws is called the following exception is thrown:
io.jsonwebtoken.security.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

I can't be completely sure this is a bug but seeing how some APIs have changed lately I am suspicious.

To Reproduce
Here is a truncated version of the class I'm writing to manage JWTs in my Spring Boot project:

import java.security.SecureRandom;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;

@Component
public class JwtUtils {
  private SecretKey jwtSigningKey;

  public JwtUtils() throws Exception {
    KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256");
    keyGenerator.init(SecureRandom.getInstanceStrong());
    jwtSigningKey = keyGenerator.generateKey();
  }

  private Claims extractAllClaims(String token) {
    return Jwts
      .parserBuilder()
      .setSigningKey(jwtSigningKey)
      .build()
      .parseClaimsJws(token)
      .getBody();
  }

  public String generateToken(UserDetails userDetails) {
    Map<String, Object> claims = new HashMap<>();
    return createToken(claims, userDetails);
  }

  public String generateToken(
    UserDetails userDetails,
    Map<String, Object> claims
  ) {
    return createToken(claims, userDetails);
  }

  private String createToken(
    Map<String, Object> claims,
    UserDetails userDetails
  ) {
    return Jwts
      .builder()
      .setClaims(claims)
      .setSubject(userDetails.getUsername())
      .claim("authorities", userDetails.getAuthorities())
      .setIssuedAt(new Date(System.currentTimeMillis()))
      .setExpiration(
        new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24))
      )
      .signWith(jwtSigningKey)
      .compact();
  }
}

When createToken is executed, a token is successfully generated and returned to an Angular frontend application. After that, when the token is successfully passed back to my code and passed into the extractAllClaims method successfully a few seconds later, the SignatureException is thrown on line 24 in the call to parseClaimsJws.

The constructor of this class, creating the secret key, also ran without problems. Because of Spring Boot's @component annotation, I know this class is a singleton in my running application and the key stays the same. I have confirmed this through logging the hash code of the key over several independent requests to my application.

Expected behavior
I expect JwtParser.parseClaimsJws to return successfully.

Project versions
io.jsonwebtoken.jjwt-api: 0.11.5
io.jsonwebtoken.jjwt-impl: 0.11.5
io.jsonwebtoken.jjwt-jackson: 0.11.5
spring-boot: 3.1.0

Thank you for the well-written and formatted bug report! I'm so grateful when submissions are easy to follow and re-create - it really helps with our motivation to help.

That said, in this case, I was unable to re-create any problems using JJWT 0.11.5 and Spring Boot 3.1.0. I copied-and-pasted your code and added a main method to test the code. I put the following in a com.example package and ran the main method:

package com.example;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import java.security.SecureRandom;
import java.util.Collection;
import java.util.Date;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.TimeUnit;

@Component
public class JwtUtils {
    private SecretKey jwtSigningKey;

    public JwtUtils() throws Exception {
        KeyGenerator keyGenerator = KeyGenerator.getInstance("HmacSHA256");
        keyGenerator.init(SecureRandom.getInstanceStrong());
        jwtSigningKey = keyGenerator.generateKey();
    }

    private Claims extractAllClaims(String token) {
        return Jwts
                .parserBuilder()
                .setSigningKey(jwtSigningKey)
                .build()
                .parseClaimsJws(token)
                .getBody();
    }

    public String generateToken(UserDetails userDetails) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, userDetails);
    }

    public String generateToken(
            UserDetails userDetails,
            Map<String, Object> claims
    ) {
        return createToken(claims, userDetails);
    }

    private String createToken(
            Map<String, Object> claims,
            UserDetails userDetails
    ) {
        return Jwts
                .builder()
                .setClaims(claims)
                .setSubject(userDetails.getUsername())
                .claim("authorities", userDetails.getAuthorities())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(
                        new Date(System.currentTimeMillis() + TimeUnit.HOURS.toMillis(24))
                )
                .signWith(jwtSigningKey)
                .compact();
    }

    public static void main(String[] args) throws Exception {

        Collection<GrantedAuthority> authorities = List.of(
                new SimpleGrantedAuthority("user"), new SimpleGrantedAuthority("admin"));

        UserDetails user = new User("test", "test", authorities);

        Map<String, Object> claimsMap = new HashMap<>();
        claimsMap.put("foo", "bar");

        JwtUtils utils = new JwtUtils();
        String jwt = utils.generateToken(user, claimsMap);
        System.out.println("JWT: " + jwt);

        Claims claims = utils.extractAllClaims(jwt);
        System.out.println("Claims:" + claims);
    }
}

No matter how many times I run this, it always works and no exceptions are thrown.

Thank you for your quick response. I have found out that the exception was related due to the idiosyncratic way in which I was testing my code. I was mislead by some factors but this is not a bug. I will close this issue.