Search code examples
javatestingspring-securityjunitjwt

Can't test generateToken() method for JWT token because of iat and exp date


I want to test my JwtService which is for generating and handling JWT Tokens. I am not even sure, if I have to test it because not all the methods are mine but I still would like to see if the token is generated correct. But if I want to test if the generated token is correct I have to compare it to another token. The only problem is the time difference, so iat (Issued At) and exp (Expiration Time/Date) are different. That's why the test always fails. How can I test even though the iat and exp are different?

JwtService:

package de.gabriel.vertretungsplan.security.service;

import de.gabriel.vertretungsplan.models.UserDetailsImpl.UserDetailsImpl;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Service;

import java.security.Key;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;

@Service
public class JwtService {

    /*
    Generated with: https://www.allkeysgenerator.com/; 256-bit key
     */
    private static final String SECRET_KEY = "secret_key";

    public String extractUsername(String token) {
        return extractClaim(token, Claims::getSubject);
    }

    public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
        final Claims claims = extractAllClaims(token);
        return claimsResolver.apply(claims);
    }

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

    public String generateToken(
            Map<String, Object> extraClaims,
            UserDetailsImpl userDetails
    ) {
        return Jwts.builder()
                .setClaims(extraClaims)
                .setSubject(userDetails.getUsername())
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + (1000 * 60 * 60 * 24 * 7))) // 7 days
                .signWith(getSigningKey(), SignatureAlgorithm.HS256)
                .compact();
    }

    public boolean isTokenValid(String token, UserDetailsImpl userDetails) {
        final String username = extractUsername(token);
        return username.equals(userDetails.getUsername()) && !isTokenExpired(token);
    }

    private boolean isTokenExpired(String token) {
        return extractExpiration(token).before(new Date());
    }

    private Date extractExpiration(String token) {
        return extractClaim(token, Claims::getExpiration);
    }

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

    private Key getSigningKey() {
        byte[] keyBytes = Decoders.BASE64.decode(SECRET_KEY);
        return Keys.hmacShaKeyFor(keyBytes);
    }

}

This is how I want to test it:

@Test
public void generateToken_shouldGenerateToken() {
    Verwaltung verwaltung = new Verwaltung(
            "user",
            "user@gmail.com",
            "user",
            Rolle.getPrefixedRolle(Rolle.USER)
    );
    String generatedToken = jwtService.generateToken(new UserDetailsImpl(verwaltung));
    assertEquals(token, generatedToken);
}

The expected token was generated on jwt.io with the same secret key and everything as reference/ expected token. Does anyone have an idea how to test it even though there is a time difference?


Solution

  • You could take a different approach and verify the contents of the JWT instead of comparing it with a pre-generated token (which contains a timestamp (iat and exp fields), so it will always be different from a pre-generated token):

    @Test
    public void generateToken_shouldGenerateToken() {
        Verwaltung verwaltung = new Verwaltung(
                "user",
                "user@gmail.com",
                "user",
                Rolle.getPrefixedRolle(Rolle.USER)
        );
        String generatedToken = jwtService.generateToken(new UserDetailsImpl(verwaltung));
    
        // parse generated token
        Claims claims = Jwts.parser()
                            .setSigningKey(DatatypeConverter.parseBase64Binary(SECRET_KEY))
                            .parseClaimsJws(generatedToken)
                            .getBody();
    
        assertEquals(verwaltung.getUsername(), claims.getSubject());
        // check if the token is not expired
        assertFalse(claims.getExpiration().before(new Date()));
    }