JWTs and Java

Mithun Khatri
4 min readSep 10, 2021
JWT with Java

JWT stands for JSON Web Token, is an open standard that defines a compact and self-contained way for securely sharing data between systems. Token contains the data in JSON format. The data is digitally signed using a secret key, and the same secret key is needed in order to extract the data out of the token, hence JWT provides secure transmission of the data (unless the secret key is compromised). JWTs can be signed using a HMAC secret or a public-private key pair using RSA.

JWT Usages

  • Authorization
    Usually user’s login credentials are used in order to access secure or protected areas on the web. Once the user has logged in, and navigates to another page in the same system, asking to enter the user credentials again is useless. Instead, system can identify the user information and their roles based on the first time login, and generate an alias (a token) which can be used for further client-server communication. JWT suites best for this use case.
  • Data exchange
    JWTs are a decent way of securely exchanging the data between different systems since the token can be signed. Also, since the signature is calculated using the header and the payload, it can be verified that the data hasn’t been modified.

JWT Structure

The token consists of three parts separated by dots.

  • Header
    Consists of type of the token and the algorithm being used.
  • Payload
    Consists of claims which can be either registered claims or user defined. Claim name should be kept small to keep the token size concise.
  • Signature
    Signature is created using header, the payload, a secret and the algorithm specified in the header.
    Signature is used to verify the data wasn’t tampered with during transmission.

Here is a sample token:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJteXZhbHVlIiwiaWF0IjoxNjI5NzE0NTc5LCJleHAiOjE2Mjk3MTQ2OTksInJvbGVzIjpbInJlYWQiLCJ3cml0ZSJdLCJzb3VyY2UiOiJtaXRodW5raGF0cmkuY29tIn0.Sw9MX_Hq7PSmkqzjukIVudUQa78xj_gJLfYZf8rjRpk
Header : eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9Payload : eyJzdWIiOiJteXZhbHVlIiwiaWF0IjoxNjI5NzE0NTc5LCJleHAiOjE2Mjk3MTQ2OTksInJvbGVzIjpbInJlYWQiLCJ3cml0ZSJdLCJzb3VyY2UiOiJtaXRodW5raGF0cmkuY29tIn0Signature: Sw9MX_Hq7PSmkqzjukIVudUQa78xj_gJLfYZf8rjRpk

There are several online tools available to create and parse the JWT token. Let’s see how can we generate and parse the token using java.

JWTs with Java

Let’s create a token using HMAC secret, and then parse the token with the same secret. We will also see what happens when an invalid token is supplied to parse using the secret, or when the secret which is used to parse the token is invalid, or the token has expired. We will add expiration time as well to the token.

We are going to use Spring boot to test out the token via rest endpoint, and maven build tool to add the required dependencies.

Create a new spring boot project from your favourite IDE or head over to start.spring.io

Add below dependencies to pom.xml

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-api</artifactId>
<version>0.11.2</version>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-impl</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>

<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt-jackson</artifactId>
<version>0.11.2</version>
<scope>runtime</scope>
</dependency>

Let’s add some common information needed to generate and parse the token to the application.yml file

jwt:
hmacSecret: 671491AE98362741F722202EED3288E8FF2508B35315ADBF75EEB3195A926B40
subject: mk01
id: mytoken
issuer: mithunkhatri.com
timeToLive: 2592000000 # 30 days

Note : There are various resources available to generate an hmac secret.

Adding a config class to read these properties

// Add lombok dependency for below annotations. Annotations are optional. Constructors and getter setters can be defined instead.@ConfigurationProperties(prefix = "jwt")
@Configuration
@Data
public class JwtConfig {

private String hmacSecret;
private String subject;
private String id;
private String issuer;
private int timeToLive;
}

Now let’s create a util class to define generate and parse methods

package com.mithunkhatri.jwt.util;

import com.mithunkhatri.jwt.configuration.JwtConfig;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.JwtBuilder;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

import javax.crypto.spec.SecretKeySpec;
import java.security.Key;
import java.util.Base64;
import java.util.Date;
import java.util.Map;

@Component
public class JwtUtil {

private final JwtConfig jwtConfig;

public JwtUtil(JwtConfig jwtConfig) {
this.jwtConfig = jwtConfig;
}

public String createJwt(Map<String, Object> claims) {
SignatureAlgorithm signatureAlgorithm = SignatureAlgorithm.HS256;

long nowInMillis = System.currentTimeMillis();
Date now = new Date(nowInMillis);

byte[] secret = Base64.getEncoder().encode(jwtConfig.getHmacSecret().getBytes());

Key signingKey = new SecretKeySpec(secret, signatureAlgorithm.getJcaName());

JwtBuilder jwtBuilder = Jwts.builder()
.setId(jwtConfig.getId())
.setIssuedAt(now)
.setSubject(jwtConfig.getSubject())
.setIssuer(jwtConfig.getIssuer())
.setClaims(claims)
.signWith(signingKey, signatureAlgorithm);

if (jwtConfig.getTimeToLive() > 0) {
jwtBuilder.setExpiration(new Date(nowInMillis + jwtConfig.getTimeToLive()));
}

return jwtBuilder.compact();
}

public Claims parseJwt(String jwt) {
return Jwts.parserBuilder()
.setSigningKey(Base64.getEncoder().encode(jwtConfig.getHmacSecret().getBytes()))
.build()
.parseClaimsJws(jwt)
.getBody();
}

}

Add a controller to communicate via rest endpoints

@RestController
@RequestMapping(value = "/jwt")
@AllArgsConstructor
public class JwtController {

private final JwtUtil jwtUtil;

@PostMapping
public String generate(@RequestBody Map<String, Object> claims) {
return jwtUtil.createJwt(claims);
}

@GetMapping(value = "/{token}")
public Claims parse(@PathVariable String token) {
return jwtUtil.parseJwt(token);
}
}

Lets try generating the token:

Request:curl --location --request POST 'http://localhost:8080/jwt' \
--header 'Content-Type: application/json' \
--data-raw '{
"nam": "mithun khatri",
"roles": [
"admin",
"user"
]
}'
Response:eyJhbGciOiJIUzI1NiJ9.eyJuYW0iOiJtaXRodW4ga2hhdHJpIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sImV4cCI6MTYzMzg2MTczMX0.VVJw90UJfvJNwIC6vYAzjJNrDiiia-_TC_DO6d-eusA

We got the token. Let’s try to parse the token now

Request:curl --location --request GET 'http://localhost:8080/jwt/eyJhbGciOiJIUzI1NiJ9.eyJuYW0iOiJtaXRodW4ga2hhdHJpIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sImV4cCI6MTYzMzg2MTczMX0.VVJw90UJfvJNwIC6vYAzjJNrDiiia-_TC_DO6d-eusA'Response:{
"nam": "mithun khatri",
"roles": [
"admin",
"user"
],
"exp": 1633861731
}

Token got parsed successfully using the same hmac secret which was used while generating the token.

Let’s try to parse the token with a dummy hmac secret

Request:curl --location --request GET 'http://localhost:8080/jwt/eyJhbGciOiJIUzI1NiJ9.eyJuYW0iOiJtaXRodW4ga2hhdHJpIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sImV4cCI6MTYzMzg2MTczMX0.VVJw90UJfvJNwIC6vYAzjJNrDiiia-_TC_DO6d-eusA'Response:
Server threw a SignatureException with a valid reason.
io.jsonwebtoken.security.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.

Now let’s try to generate a token with very small time to live. And then try to parse the token. Changed the timeToLive in application.yml to 2 seconds.

Request:curl --location --request GET 'http://localhost:8080/jwt/eyJhbGciOiJIUzI1NiJ9.eyJuYW0iOiJtaXRodW4ga2hhdHJpIiwicm9sZXMiOlsiYWRtaW4iLCJ1c2VyIl0sImV4cCI6MTYzMTI3MDExNn0.CumEP7zKJdVxXfd2eps8U5smJ7EZBsE7LyW9DWChotc'Response:
Server threw a ExpiredJwtException with a valid reason.
io.jsonwebtoken.ExpiredJwtException: JWT expired at 2021-09-10T10:35:16Z. Current time: 2021-09-10T10:35:24Z, a difference of 8687 milliseconds. Allowed clock skew: 0 milliseconds.

References:

✌️

--

--