JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted

Viewed 39093

I am building a server side REST service application. I have a problem with the JWT authentication token. I can get the token easily after sign in (Here I use Postman).

enter image description here

But when I am trying to authenticate a request to access a protected REST controller using the same token, I get the following error:

io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.
    at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:354)
    at io.jsonwebtoken.impl.DefaultJwtParser.parse(DefaultJwtParser.java:481)
    at io.jsonwebtoken.impl.DefaultJwtParser.parseClaimsJws(DefaultJwtParser.java:541)
    at com.configuration.jwt.JwtTokenUtil.extractClaims(JwtTokenUtil.java:104)
    at com.configuration.jwt.JwtTokenUtil.getUsernameFromToken(JwtTokenUtil.java:39)
    at com.configuration.jwt.JwtAuthenticationFilter.doFilterInternal(JwtAuthenticationFilter.java:44)
    at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:107)
...

it's like the application doesn't remember the token it generated. Here is the get request from Postman that generated this error:

enter image description here

I guess the source of the exception is from the method extractClaims of my class JwtTokenUtil:

@Component
public final class JwtTokenUtil {

    public static final int EXPIRATION_IN_SECONDS = 120;

    private static final String JWT_SECRET = "Some$ecretKey";

    private Clock clock = DefaultClock.INSTANCE;

    @Value("${jwt.secret}")
    private String secret;

    @Value("${jwt.expiration}")
    private Long expiration;

    private JwtTokenUtil() {
        // Hide default constructor
    }

    public String getUsernameFromToken(String token) {
        return extractClaims(token).getSubject();
    }

    public Boolean validateToken(String token, UserDetails userDetails) {
        UserDetailsImp user = (UserDetailsImp) userDetails;
        final String username = getUsernameFromToken(token);
        return (username.equals(user.getUsername()) && !isTokenExpired(token));
    }

    public Date getIssuedAtDateFromToken(String token) {
        return extractClaims(token).getIssuedAt();
    }

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

    private String doGenerateToken(Map<String, Object> claims, String subject) {
        final Date createdDate = clock.now();
        final Date expirationDate = calculateExpirationDate(createdDate);

        return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(createdDate)
                .setExpiration(expirationDate).signWith(SignatureAlgorithm.HS512, secret).compact();
    }

    private Date calculateExpirationDate(Date createdDate) {
        return new Date(createdDate.getTime() + expiration * 1000);
    }

    public static String createToken(String username, Date issueDate) {
        String jwtToken = Jwts.builder().setSubject(username).setIssuedAt(issueDate)
                .setExpiration(new Date(issueDate.getTime() + EXPIRATION_IN_SECONDS))
                .signWith(SignatureAlgorithm.HS512, JWT_SECRET).compact();

        return jwtToken;
    }

    public static String getSubject(String token) {
        Claims claims = extractClaims(token);
        return claims.getSubject();
    }

    public static String refreshToken(String token, long expirationInSeconds) {
        final Claims claims = extractClaims(token);

        Date now = new Date();
        claims.setIssuedAt(now);
        claims.setExpiration(new Date(now.getTime() + EXPIRATION_IN_SECONDS));

        return createTokenFromClaims(claims);
    }

    public static boolean isTokenExpired(String token) {
        final Claims claims = extractClaims(token);
        Date now = new Date();

        return now.after(claims.getExpiration());
    }

    private static String createTokenFromClaims(Claims claims) {
        return Jwts.builder().setClaims(claims).signWith(SignatureAlgorithm.HS512, JWT_SECRET).compact();
    }

    private static Claims extractClaims(String token) {
        return Jwts.parser().setSigningKey(JWT_SECRET).parseClaimsJws(token).getBody();
    }

}

This is my JwtAuthenticationFilter class:

public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private UserDetailsService userDetailsService;

    @Autowired
    private JwtTokenUtil jwtTokenUtil;

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws IOException, ServletException {

        String header = req.getHeader("Authorization");
        String username = null;
        String authToken = null;

        if (header != null && header.startsWith("Bearer ")) {

            authToken = header.replace("Bearer ", "");

            try {

                username = jwtTokenUtil.getUsernameFromToken(authToken);

            } catch (IllegalArgumentException e) {

                logger.error("an error occured during getting username from token", e);

            } catch (ExpiredJwtException e) {

                logger.warn("the token is expired and not valid anymore", e);
            }
        } else {
            logger.warn("couldn't find bearer string, will ignore the header");
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

            UserDetails userDetails = userDetailsService.loadUserByUsername(username);

            if (jwtTokenUtil.validateToken(authToken, userDetails)) {

                String role = "";

                role = userDetails.getAuthorities().size() > 1 ? "ROLE_ADMIN" : "ROLE_TOURIST";

                UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
                        userDetails, null, Arrays.asList(new SimpleGrantedAuthority(role)));

                authentication.setDetails(new WebAuthenticationDetailsSource().buildDetails(req));

                logger.info("authenticated user " + username + ", setting security context");

                SecurityContextHolder.getContext().setAuthentication(authentication);
            }
        }

        chain.doFilter(req, res);
    }
}

and I don't know if the sign in controller have anything to do with the issue, but here is the code for it anyway:

@PostMapping(value = "/signin")
    public ResponseEntity<?> signin(@Valid @RequestBody LoginForm loginForm) throws AuthenticationException {

        final Authentication authentication = authenticationManager.authenticate(
                new UsernamePasswordAuthenticationToken(loginForm.getUsername(), loginForm.getPassword()));
        SecurityContextHolder.getContext().setAuthentication(authentication);

        final UserDetails user = userService.loadUserByUsername(loginForm.getUsername());

        final String token = jwtTokenUtil.generateToken(user);

        return ResponseEntity.ok(new JwtResponse(token, user.getUsername(), user.getAuthorities()));
    }

I hope somebody can help.

5 Answers

I know it is old question, but I had the same problem and found a solution that works for me so it might be helpful for someone else.

The hashing algorithm converts the provided secret (String value) into a byte array and then performs encoding. The created token is based on bytes and when you try to use a String value to decode the token, error is thrown. Solution was to use bytes arrays as a secret and provide it to decoding function. You can simply turn a String value into a byte array using:

byte[] secret = stringSecret.getBytes();

and use it for decoding.

In your case it would be a little change in the JwtTokenUtil.class:

private static Claims extractClaims(String token) {
    return Jwts.parser().setSigningKey(JWT_SECRET.getBytes()).parseClaimsJws(token).getBody();
}

I think EXPIRATION_IN_SECONDS should be in milliseconds, because you're adding it to getTime(), which is in milliseconds. So it should be 120000 actually.

Got the same issue, in my case the token passing from angular has quotes in start/end. Resolved by removing them.

intercept(request: HttpRequest<any>, next: HttpHandler): Observable<HttpEvent<any>> {
    let token = localStorage.getItem('token');
    if (token) {
        token = token.replace(/^"(.*)"$/, '$1');
    }

    if (token) {
        request = request.clone({ headers: request.headers.set('Authorization', 'Bearer ' + token) });
    }

    if (!request.headers.has('Content-Type')) {
        request = request.clone({ headers: request.headers.set('Content-Type', 'application/json') });
    }

    request = request.clone({ headers: request.headers.set('Accept', 'application/json') });
    console.log("............");
    return next.handle(request);
}

   

Had similar issue with same error message. I realized it was due to white spacing in the public key provided. You might check it out. Fixed with this code snippet:

   String PUB_KEY = System.getenv("PUBLIC_KEY") ;  // remove ---PUBLIC KEY--- & ---END PUBLIC KEY ---
   String PUBLIC_KEY = "";
        if (!PUB_KEY.isEmpty()) {
            PUBLIC_KEY = PUB_KEY.replace(" ", "");
        }

Hope it helps.

I had this error while trying to parse my JWT BUT my mistake was using two different instances of my TokenUtils.class, and they had different keys because I delegated the Key creating to the lib.

SOLUTION: Created a @Component anotated TokenUtilsBean class with a @Bean annotated method returning a instance of TokenUtils.class. So I used dependency injection to get the same instance on my AuthFilter.class and CustomUserService.class.

@Component
public class TokenUtilsBean {

  @Bean
  public TokenUtils tokenUtils() {
    return new TokenUtils();
  }
}

Here's my class and the JUnit tests, maybe someone is doing this wrongly like me.


public class TokenUtils {
  private final KeyPair keys = Keys.keyPairFor(SignatureAlgorithm.RS512);
  private final ObjectMapper mapper = new ObjectMapper();

  public String generateToken(CustomUser user) {
    Map<String, Object> claims = mapper.convertValue(user, Map.class);

    return Jwts.builder()
            .setSubject(user.getUsername())
            .setClaims(claims)
            .signWith(keys.getPrivate())
            .compact();
  }

  public Boolean validateToken(String jwt) {
    try {
      Jwts.parserBuilder()
              .setSigningKey(keys.getPublic())
              .build()
              .parseClaimsJws(jwt);

      return true;
    } catch (Exception e) {
      return false;
    }
  }
}


Here's the JUnit Unit tests

  @Test
  public void validateTokenWithDifferentInstanceShouldBeFalse() {
    String jwt = tokenUtils.generateToken(user);

    //Validating token with a different instance
    Assertions.assertFalse(secondTokenUtils.validateToken(jwt));
  }
Related