Spring Boot Security Example - Refresh Expired JSON Web Token
Suppose our requirement is such that if the token has expired, still the user should be allowed to access the system if the token is valid. That is the token should be refreshed or a new valid token should be provided.
We will be working on a solution where if the user he receives JWT expired exception, then he can call another API with the expired token. A new token will then provided to the user which he can use for future interactions. Previously we had implemented an example for programmatically consuming the JWT secure API using Spring RestTemplate. We will be testing this refresh Token generation API both using Postman as well as the Spring RestTemplate.
Video
This tutorial is explained in the below Youtube Video.Spring Boot JSON Web Token- Table of Contents
Understanding the need for JSON Web Token(JWT) Understanding JWT Structure Implement Spring Boot Security Implement Spring Boot + JSON Web Token Security Implement Spring Boot Security + JSON Web Token + MySQL Spring Boot RestTemplate + JWT Authentication Example Spring Boot Security - Refresh Expired JSON Web Token Angular 7 + Spring Boot JWT Authentication Hello World Example Online JWT Generator Online JWT Decoder
Create and return new JWT token on Expiration
We will be modifying the Spring Boot + JWT + MySql example to implement Refresh JWT.In application properties specify expiration time for the refresh token to be created. We use a seperate value for Refresh Token as we may want to specify different value to refresh token that the original JWT.
Also the expirationDateInMs we have specified as 0 because we want to test the expiration scenario.
jwt.secret = javainuse jwt.expirationDateInMs=0 jwt.refreshExpirationDateInMs=9000000 spring.datasource.url=jdbc:mysql://localhost/bootjwt?createDatabaseIfNotExist=true&autoReconnect=true&useSSL=false spring.datasource.username=root spring.datasource.password=root spring.jpa.hibernate.ddl-auto=create-dropIn the JwtUtil class create a method named doGenerateRefreshToken to create the refresh token.
package com.javainuse.springbootsecurity.config; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.HashMap; import java.util.List; import java.util.Map; import org.springframework.beans.factory.annotation.Value; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.authority.SimpleGrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import io.jsonwebtoken.Claims; import io.jsonwebtoken.ExpiredJwtException; import io.jsonwebtoken.Jws; import io.jsonwebtoken.Jwts; import io.jsonwebtoken.MalformedJwtException; import io.jsonwebtoken.SignatureAlgorithm; import io.jsonwebtoken.SignatureException; import io.jsonwebtoken.UnsupportedJwtException; @Service public class JwtUtil { private String secret; private int jwtExpirationInMs; private int refreshExpirationDateInMs; @Value("${jwt.secret}") public void setSecret(String secret) { this.secret = secret; } @Value("${jwt.expirationDateInMs}") public void setJwtExpirationInMs(int jwtExpirationInMs) { this.jwtExpirationInMs = jwtExpirationInMs; } @Value("${jwt.refreshExpirationDateInMs}") public void setRefreshExpirationDateInMs(int refreshExpirationDateInMs) { this.refreshExpirationDateInMs = refreshExpirationDateInMs; } public String generateToken(UserDetails userDetails) { Map<String, Object> claims = new HashMap<>(); Collection<? extends GrantedAuthority> roles = userDetails.getAuthorities(); if (roles.contains(new SimpleGrantedAuthority("ROLE_ADMIN"))) { claims.put("isAdmin", true); } if (roles.contains(new SimpleGrantedAuthority("ROLE_USER"))) { claims.put("isUser", true); } return doGenerateToken(claims, userDetails.getUsername()); } private String doGenerateToken(Map<String, Object> claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + jwtExpirationInMs)) .signWith(SignatureAlgorithm.HS512, secret).compact(); } public String doGenerateRefreshToken(Map<String, Object> claims, String subject) { return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + refreshExpirationDateInMs)) .signWith(SignatureAlgorithm.HS512, secret).compact(); } public boolean validateToken(String authToken) { try { Jws<Claims> claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(authToken); return true; } catch (SignatureException | MalformedJwtException | UnsupportedJwtException | IllegalArgumentException ex) { throw new BadCredentialsException("INVALID_CREDENTIALS", ex); } catch (ExpiredJwtException ex) { throw ex; } } public String getUsernameFromToken(String token) { Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); return claims.getSubject(); } public List<SimpleGrantedAuthority> getRolesFromToken(String token) { Claims claims = Jwts.parser().setSigningKey(secret).parseClaimsJws(token).getBody(); List<SimpleGrantedAuthority> roles = null; Boolean isAdmin = claims.get("isAdmin", Boolean.class); Boolean isUser = claims.get("isUser", Boolean.class); if (isAdmin != null && isAdmin) { roles = Arrays.asList(new SimpleGrantedAuthority("ROLE_ADMIN")); } if (isUser != null && isAdmin) { roles = Arrays.asList(new SimpleGrantedAuthority("ROLE_USER")); } return roles; } }