In this tutorial, you will be introduced to the process of adding JSON Web Token (JWT) support to your Java application. You will also learn how to add and validate custom JWT Claims using the io.jsonwebtoken library. The JWT tokens generated in this process will be signed with a SecretKeySpec, making them secure and tamper-proof.
Also, this tutorial assumes that you have prior knowledge of creating Java projects using Maven. If not, I recommend you read the following tutorial: Create Java Project with Maven.
Add io.jsonwebtoken Libraries to POM.XML
Adding JWT support to your Java application is very simple. All you need to do is include the io.jsonwebtoken library by adding the following Maven dependencies to your pom.xml file.
<dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.11.5</version> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-impl --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.11.5</version> </dependency> <!-- https://mvnrepository.com/artifact/io.jsonwebtoken/jjwt-jackson --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency>
Generate Simple JWT
To generate a new JWT I will use the Jwts.builder(), which comes with the JJWT dependency we have added to a pom.xml file.
Instant now = Instant.now(); Instant expiration = now.plusSeconds(3600); // expires in 1 hour Date expDate = Date.from(expiration); byte[] secretKeyBytes = Base64.getEncoder().encode(secret.getBytes()); SecretKey secretKey = new SecretKeySpec(secretKeyBytes, SignatureAlgorithm.HS512.getJcaName()); String token = Jwts.builder() .setSubject(subject) .setIssuedAt(Date.from(now)) .setExpiration(expDate) .signWith(secretKey, SignatureAlgorithm.HS512) .compact();
The following code generates a JSON Web Token (JWT). A JWT is a compact and self-contained way to transmit information between parties. The information is encoded as a JSON object and then signed with a digital signature to ensure its authenticity and integrity.
The code starts by using the Instant.now()
method to get the current time. Then, it calculates the expiration time by adding 3600 seconds (1 hour) to the current time using the plusSeconds
method. The Date
object is then created from the Instant
object to store the expiration date.
Next, the secret key is created from the secret
string. The secret key bytes are first encoded using the Base64
encoder and then used to create a SecretKey
object using the SecretKeySpec
class.
Finally, the JWT token is generated using the Jwts
builder. The builder is configured with the following information:
setSubject
: The subject of the JWT, which is passed as a parameter.setIssuedAt
: The time the JWT was issued, which is set to the current time.setExpiration
: The time the JWT will expire, which is set to the expiration date calculated earlier.signWith
: The secret key used to sign the JWT and the signature algorithm used, which isSignatureAlgorithm.HS512
.
The compact
method is then called to generate the final JWT token as a compact, URL-safe string.
Complete Code Example
/** * * @author sergeykargopolov */ import io.jsonwebtoken.Jwts; import io.jsonwebtoken.SignatureAlgorithm; import java.time.Instant; import java.util.Base64; import java.util.Date; import javax.crypto.SecretKey; import javax.crypto.spec.SecretKeySpec; public class JWTExample { private static final String subject = "user"; private static final String secret = "snjdu438fkdj38fdmcv7dm3ckvhrsnjdu438fkdj38fdmcv7dm3ckvhr"; public static void main(String[] args) { Instant now = Instant.now(); Instant expiration = now.plusSeconds(3600); // expires in 1 hour Date expDate = Date.from(expiration); byte[] secretKeyBytes = Base64.getEncoder().encode(secret.getBytes()); SecretKey secretKey = new SecretKeySpec(secretKeyBytes, SignatureAlgorithm.HS512.getJcaName()); String token = Jwts.builder() .setSubject(subject) .setIssuedAt(Date.from(now)) .setExpiration(expDate) .signWith(secretKey, SignatureAlgorithm.HS512) .compact(); System.out.println("JWT Token: " + token); } }
If you run the above application, you will get the JWT printed:
JWT Token: eyJhbGciOiJIUzUxMiJ9.eyJzdWIiOiJ1c2VyIiwiaWF0IjoxNjc2MjQ5MTA0LCJleHAiOjE2NzYyNTI3MDR9.8n2MF3c17IrVrK60ZMq3_6JjbQ-R6coXe0L_wK0rLD0Dt0rITOeu_LMXXV43KRGroeeo9IrrUaI4CoO-qgu4EQ
The JWT is Base64 encoded, and you can easily decode it with any Base64 decoder online tool. Because the content of JWT can be easily decoded, you should never place a user’s sensitive information into the JWT token. For example, never include the user’s password or token secret with which the JWT was signed in the body of the JWT token.
Validate JWT
To validate the JWT we will need to use the same signing key with which the JWT was earlier signed. If the token secret is not correct, the following error will be thrown when JWT is validated: “io.jsonwebtoken.SignatureException: JWT signature does not match locally computed signature. JWT validity cannot be asserted and should not be trusted.”
You can use the following code snippet to validate JWT and read the subject value.
JwtParser jwtParser = Jwts.parserBuilder() .setSigningKey(secretKey) .build(); Jwt<Header, Claims> jwt = jwtParser.parse(token); String extractedSubject = jwt.getBody().getSubject(); System.out.println(extractedSubject);
In this code, the JwtParser
is first configured to use the specified secret key to verify the signature of the JWT. The JWT is then parsed using the parse
method, and the subject is extracted from the claims using the getSubject
method. The extracted subject is then printed to the console.
Add Custom Claims to JWT
Claims live in the Body of JWT. JWT Claims are pieces of information that are asserted to the subject and are key-value pairs. There are claims that are reserved to ensure interoperability with third-party, or external, applications, and there are custom claims that can be added to JWT by you. If needed, you can add additional pieces of information(custom claims) about the subject or the user for which the JWT is created.
Reserved Claims
Let’s have a look at the list of reserved claims.
iss – Issuer,
sub – Subject,
aud – Audience,
exp – Expiration,
nbf – Not Before,
iat – Issued At,
jti – JWT ID
Custom Claims
Custom claims are custom key-value pairs that you can add to the body of JWT. It can be a user Role or a Privilege; it can be the user’s department at work or anything else you need to add to JWT. For example, in the below code snippet, I am adding two custom claims to JWT, which are the user’s Role and Department at work.
// Generate GWT String token = Jwts.builder() .setSubject(subject) .setIssuedAt(Date.from(now)) .setExpiration(expDate) .claim("Role", "Admin") .claim("Department", "Product development") .signWith(secretKey, SignatureAlgorithm.HS512) .compact();
In the above code example, I have added two custom claims: Role and Department. If needed, you can add more claims to the body of JWT. Just remember not to add sensitive information like a user password or token secret. JWT claims can be decoded and viewed.
Read Claims from JWT
To read the custom Claims from the body of the JWT token, you can use the following code snippet.
JwtParser jwtParser = Jwts.parserBuilder() .setSigningKey(secretKey) .build(); Jwt<Header, Claims> jwt = jwtParser.parse(token); Claims claims = jwt.getBody(); // Reading Reserved Claims System.out.println("Subject: " + claims.getSubject()); System.out.println("Expiration: " + claims.getExpiration()); // Reading Custom Claims System.out.println("Role: " + claims.get("Role")); System.out.println("Department: " + claims.get("Department"));
In this example, the JWT is first parsed using the JwtParser
class and the specified secret key. The parsed JWT is then represented as a Jwt
object, which has two parts: a header and a body. The body is represented as a Claims
object, which is used to access the claims in the JWT. To access a specific claim, you can use the .get
method, which takes the name of the claim as its first argument and the type of the claim as its second argument.
Remember that JWT is a Base64 encoded string and can be easily decoded. Therefore, you should not put into Claims any user details that are sensitive. Even though the information in Claims cannot be altered, this information can be viewed by the Base64-decoding JWT token.
Conclusion
I hope this tutorial was helpful to you. If you want to learn more about Spring Boot and Spring Security check out my other tutorials on this blog. If you are interested to learn about Microservices and Spring Cloud, then check out this page: Spring Cloud.
If you enjoy learning by following step-by-step online video courses, then have a look at the below list of online video courses that teach Spring Security. One of them might be very helpful.