Spring Boot HandBook

    Introduction :#

    JWT (JSON Web Token) is a digitally signed token used to securely transmit information between parties. It acts like a digital passport, allowing users to access parts of a web application without needing to log in repeatedly. The token contains necessary data, and its signature ensures the data hasn’t been tampered with. JWT enables stateless authentication, where the server doesn’t need to remember users, but can still trust the provided information.

    Why use JSON Web Token (JWT)#

    JWTs provide a secure, scalable, and stateless solution for authentication and authorization, particularly in distributed systems like microservices. Their self-contained nature eliminates the need for session storage, simplifying server architecture and improving scalability. JWTs enable cross-domain authentication, making them ideal for Single Sign-On (SSO). They are also well-suited for decentralized systems, where each microservice can independently verify the token. With strong security features like digital signatures and encryption, JWTs ensure the integrity of data, protecting sensitive information. In summary, JWTs enhance performance, security, and scalability across modern applications.

    Implementation of how to create JWT :#

    JWT Dependencies

    Add these dependencies to your pom.xml in a Maven project to enable JWT handling within your Spring Boot application. With these, you can easily create, sign, and verify JWT tokens in your application.

    <!--This provides the core API for generating, parsing, and validating JWTs. It contains all the interfaces and classes necessary for creating and managing JWTs. --> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-api</artifactId> <version>0.12.6</version> </dependency> <!--This dependency provides the implementation of the JWT functionality specified by the jjwt-api. It's marked as runtime, meaning it is only required at runtime and not during compilation.--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-impl</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency> <!--This provides support for JSON serialization and deserialization using Jackson. It allows JWT payloads to be serialized into JSON and deserialized back into Java objects.--> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.12.6</version> <scope>runtime</scope> </dependency>
    1. We have a User also which implements the UserDetails.
    //This is our 'User' import jakarta.persistence.*; import lombok.*; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.userdetails.UserDetails; import java.util.Collection; import java.util.List; @Entity @Getter @Setter @NoArgsConstructor @AllArgsConstructor @ToString public class UserEntity implements UserDetails { @Id @GeneratedValue(strategy = GenerationType.IDENTITY) private Long id; @Column(unique = true)//indicate that email should be unique private String email; private String password; private String name; //we have to implement these three methods @Override public Collection<? extends GrantedAuthority> getAuthorities() { return List.of(); } @Override public String getPassword() { return this.password; } @Override public String getUsername() { return this.name; } }
    1. We need a Sercive class. Here I’m using JWTService class to generate the JWT token. For this we have to create a Service. Here basically we need two methods one for creation of JWT and another one for validation of JWT.
    import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.stereotype.Service; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Set; //this is our service class @Service public class JWTService { //initialize a secreteKey // You can use any random key, but it must be a 256-bit key (32 bytes) for HMAC-SHA256 private static final String jwtSecreteKey = "ddgdbydjsmsjjsmhdgdndjsksjbdddjdkddk"; //generate a secreate key method private SecretKey generateSecreteKey(){ return Keys.hmacShaKeyFor(jwtSecreteKey.getBytes(StandardCharsets.UTF_8)); } //create token method public String createToken(UserEntity user) { return Jwts.builder() //to build .subject(user.getId().toString()) // here we get the id which is in UserEntity class and Id is Long so we use toString() .claim("email",user.getEmail()) //in claim we have to add user details .claim("roles", Set.of("ADMIN","USER")) // if you define any role then you can use it like this .issuedAt(new Date()) // every token has a issueAt date .expiration(new Date(System.currentTimeMillis()+1000*60)) // also has an expiration time, such as expiring 1 minute after creation .signWith(generateSecreteKey()) //signwith the key which is generated .compact(); // all of above need to compact } }

    JWT Verification#

    The server checks if the message was altered by verifying the signature. If the signature is valid, the message is trusted; otherwise, it's rejected.

    • Sample Token:
    eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
    • Token Split:

      The token is split into its three parts (header, payload, signature) using the period (.) as the delimiter.
       
    • Recreation of the Control Signature:

      • The header and payload are concatenated and hashed using the HMAC-SHA256 algorithm with the same secret key used during creation.
      • This produces the encoded control signature.
         
    • Signature Comparison:

      • The control signature is compared with the received signature.
      • If they match, the token is considered valid. If not, the token is invalid.

    how to verify our token:#

    Create a method that will verify the token that you are provided.

    import io.jsonwebtoken.Jwts; import io.jsonwebtoken.security.Keys; import org.springframework.stereotype.Service; import javax.crypto.SecretKey; import java.nio.charset.StandardCharsets; import java.util.Date; import java.util.Set; //this is our service class @Service public class JWTService { //initialize a secreteKey //generate a secreate key method //create token method //to verify that token we use this method basically this method will give us the id that we are using to create this token public Long generateUserIdFromToken(String token) { Claims claims = Jwts.parser() // to parse it .verifyWith(generateSecreteKey())// verify the key that we have .build() // build the parser .parseSignedClaims(token) //parse the token and get jws<claims> .getPayload(); //to get the payload return Long.valueOf(claim.getSubject()); //Assuming the user ID is stored as the subject in the token }

    After that you can also test it using Test folder of your Application.

    import org.junit.jupiter.api.Test; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; @SpringBootTest class JWTApplicationTests { @Autowired private JWTService jwtService; @Test void contextLoads() { UserEntity user = new UserEntity(<user id>,<user email>,<user password>,<user name>); String token = jwtService.createToken(user); System.out.println("Create Token: "+token); Long id = jwtService.generateUserIdFromToken(token); //to varify the token you can directly use that token here System.out.println("Generate Id from Token: "+id); } }

    After run this test file you can see the output. Here I am using user id = 4 that’s why I am getting this output.

    You can also verify your token by using jwt.io .

    If you use a wrong token like header and payload are correct but you put the wrong signature.

    @SpringBootTest class JWTApplicationTests { @Autowired private JWTService jwtService; @Test void contextLoads() { UserEntity user = new UserEntity(<user id>,<user email>,<user password>,<user name>); String token = jwtService.createToken(user); // create the token System.out.println("Create Token: "+token); Long id = jwtService.generateUserIdFromToken("write the token that you have already created with wrong signature"); //to varify the token you can directly use that token here System.out.println("Generate Id from Token: "+id); } }

    You can see SignatureException.

    Even if you use the correct token after 1 minute of its creation, it will have expired and the application throws a ExpiredJwtException .

    Last updated on Dec 27, 2024