Search Tutorials


Spring Boot 3 + JWT Hello World Example

Spring Boot 3 + JWT Hello World Example

In previous tutorial we implemented Spring Boot 3 + Basic authentication simple example where we implemented basic authentication and understood its internal working. Also in another previous tutorial we had implemented Spring Boot 3 + MySQL + CRUD example. We will be modifying this example to implement JWT Authentication.
Also in the next tutorial we will be implementing swagger configuration for the Spring Boot 3 + JWT that we will be implementing in this tutorial.

Video

This tutorial is explained in the below Youtube Video.

Drawback of Basic authentication

Basic authentication in Spring Security is a very simple way to authenticate users based on their username and password. When a user tries to access a protected resource, they are prompted to enter their username and password.
Suppose Gmail makes use of Basic Authentication. So if a user needs to access the inbox page he will need to send the username and password to access this page. Now suppose the user want to next access the sent mail page. If now suppose he sends the request with username and password he will not be able to access it. Because Basic Authentication request needs username and password with each request.

Drawbacks-
  • Basic Authentication requires the credentials to be sent along with each request. This can can cause man in the middle attack
  • Suppose we need to give access to a particular user for only 1 day. This is not possible with Basic Auth
  • Basic Auth Request contains only the credentials. It cannot have additional request information like the user roles or some other custom information

Drawbacks of session management using session id's and cookies

In case of session management the user will send the username and password to the server only once. Once the user is authenticated the server returns back a unique session id. These session ids are usually contained within cookies. For all future requests the user does not need to pass the username and password with the requests. Also then for all subsequent requests and response, this session id will also be passed. So now when the server receives a request it will check the session id. Using this session id will check if there is any corresponding information. It will then allow the user to access the resource and return back the response along with the session id.
Again consider the Gmail scenario. Here the user will send the username and password only once when accessing the inbox page. Once authenticated then gmail will return a session id along with the response. User can then make use of this session id for accessing other pages like the sent mails page.

Drawbacks-
Session Id is not self contained. It is a reference token. During each validation the Gmail server needs to fetch the information corresponding to it.
Not suitable for microservices architecture involving multiple API's and servers. It is less scalable.




JSON Web Token

Similar to session management using session id, in case of JWT also the user will need to pass the username and password once to the server. Once authenticated the server returns back a JWT which can be used for all subsequent requests.
Let us again consider the Gmail scenario. Here the user will send the username and password only once when accessing the inbox page. Once authenticated then gmail will return a JWT along with the response. User can then make use of this session id for accessing other pages like the sent mails page. Also if the user needs to suppose access other google service like google drive or youtube it can do so using this JWT.

JWT has following advantages over session management using session id/cookies.

Advantages -
JWT is self contained. It is a value token. So during each validation the Gmail server does not needs to fetch the information corresponding to it.
It is digitally signed so if any one modifies it the server will know about it.
It is most suitable for Microservices Architecture.
It has other advantages like specifying the expiration time.

Structure of JWT

A JWT consists of 3 parts -





  • Header
  • Payload
  • Signature
This is how a JWT token looks like-

The 3 parts of JWT are seperated by a dot.Also all the information in the 3 parts is in base64 encoded format.

Let us have a look at each of the three parts of JWT and its functionality.

An important point to remember about JWT is that the information in the payload of the JWT is visible to everyone. There can be a "Man in the Middle" attack and the contents of the JWT can be changed. So we should not pass any sensitive information like passwords in the payload. We can encrypt the payload data if we want to make it more secure. However we can be sure that no one can tamper and change the payload information. If this is done the server will recognize it.

Creating a JWT Token

We will be creating a JWT token using JWT Online Token Generator
Specify the payload data as folows-
Create JWT

Inspect the contents of the created token

We will be inspecting JWT token using JWT Online Decoder

JWT decode

Implementation

We will be modifying the code we had implemented previously for Spring Boot 3 + MySQL + CRUD example.
We will first be modifying the pom.xml to add the spring security and jwt dependencies.
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
	xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
	<modelVersion>4.0.0</modelVersion>
	<parent>
		<groupId>org.springframework.boot</groupId>
		<artifactId>spring-boot-starter-parent</artifactId>
		<version>3.2.2</version>
		<relativePath/> <!-- lookup parent from repository -->
	</parent>
	<groupId>com.javainuse</groupId>
	<artifactId>boot-mysql-crud</artifactId>
	<version>0.0.1-SNAPSHOT</version>
	<name>boot-mysql-crud</name>
	<description>Demo project for Spring Boot</description>
	<properties>
		<java.version>17</java.version>
	</properties>
	<dependencies>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-data-jpa</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-web</artifactId>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-security</artifactId>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-api</artifactId>
			<version>0.11.5</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-impl</artifactId>
			<version>0.11.5</version>
		</dependency>
		<dependency>
			<groupId>io.jsonwebtoken</groupId>
			<artifactId>jjwt-jackson</artifactId>
			<version>0.11.5</version>
		</dependency>

		<dependency>
			<groupId>com.mysql</groupId>
			<artifactId>mysql-connector-j</artifactId>
			<scope>runtime</scope>
		</dependency>
		<dependency>
			<groupId>org.springframework.boot</groupId>
			<artifactId>spring-boot-starter-test</artifactId>
			<scope>test</scope>
		</dependency>
	</dependencies>

	<build>
		<plugins>
			<plugin>
				<groupId>org.springframework.boot</groupId>
				<artifactId>spring-boot-maven-plugin</artifactId>
			</plugin>
		</plugins>
	</build>

</project>
For implementation of JWT we will divide the workflow in 2 parts
  • Using the url /authenticate we will be generating the JWT.
  • Whenever the user passes the JWT along with the request, the server will validate it.

Generate JWT

Using the /authenticate url the user will be generating a JWT. For this we will be whitelisting the url /authenticate in spring security. Also along with this request we will be passing the username and password.
Spring Boot 3 security JWT authentication
We will be having the following workflow
  1. The user hits the url /authenticate along with the credentials.
  2. We will be defining the spring security configuration such that the /authenticate url will be whitelisted.
  3. In the filterchain various filters will get added and called.
  4. One of these filters will be a custom filter we will be writing named JWTRequestFilter. This filter will be responsible for intercepting the incoming request. It then checks if the request has a valid JWT.
  5. If it has then the JWT will be validated. If not then this class does nothing. So in our case this filter will do nothing.
  6. Authorization Filter is the last filter that gets called in the filter chain. It checks if the previous flters have already authenticated the request and populated the security context. If not then this filter it throws an exception. In case of the /authenticate url, we have whitelisted this url. So this request can pass through the filter chain unauthenticated.
  7. We will be writing a custom controller named JWT Controller which has a url post mapping for /authenticate. In this controller method we get the credentials from the user request and authenicate them using the UserDetailsImpl service that we will be writing. If the authentication is successful we call the JWTTokenUtil class.
  8. In the JWTTokenUtil class we generate a token and this token is returned back to the user by the JWTController.
Next we will be creating a custom filter class named JwtRequestFilter. The JwtRequestFilter is a filter that intercepts incoming HTTP requests and performs some actions before the request is passed to the next filter or resource.
The JwtRequestFilter class extends the OncePerRequestFilter class, which is a base class for filters that should only be executed once per request. This ensures that the filter logic is only applied once, even if the request goes through multiple filter chains. Inside the doFilterInternal method, the filter performs the following actions:
  • 1. It retrieves the value of the "Authorization" header from the incoming request using request.getHeader("Authorization").
  • 2. It checks if the obtained token starts with the string "Bearer ".
  • 3. If the token starts with "Bearer ", it can be assumed to be a JWT (JSON Web Token). The filter can then perform additional processing on the token, such as validation or extracting user information.
  • 4. If the token does not start with "Bearer ", a warning message is logged.
  • 5. Finally, the request and response objects are passed to the next filter or resource in the filter chain using filterChain.doFilter(request, response).
package com.javainuse.bootmysqlcrud.config;

import java.io.IOException;

import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

	@Override
	protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request,
			jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain)
			throws jakarta.servlet.ServletException, IOException {

		final String requestTokenHeader = request.getHeader("Authorization");

		// JWT Token is in the form "Bearer token". Remove Bearer word and get only the
		// Token
		if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {

		} else {
			logger.warn("JWT Token does not begin with Bearer String");
		}
		filterChain.doFilter(request, response);
	}
}
The JwtUserDetailsService class is a service class that implements the UserDetailsService interface. It provides a method called loadUserByUsername, which takes a username as input and returns a UserDetails object. In this specific implementation, if the username is "javainuse", it creates a new User object with this username, a hashed password, and an empty list of authorities. If the username is not "javainuse", it throws a UsernameNotFoundException. This class is typically used for authentication purposes in a web application that uses JSON Web Tokens (JWT) for user authentication and authorization.
package com.javainuse.bootmysqlcrud.service;

import java.util.ArrayList;

import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.stereotype.Service;

@Service
public class JwtUserDetailsService implements UserDetailsService {

	@Override
	public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
		if ("javainuse".equals(username)) {
			return new User("javainuse", "$2a$10$slYQmyNdGzTn7ZLBXBChFOC9f6kFjAqPhccnP6DxlWXx2lPk1C3G6",
					new ArrayList<>());
		} else {
			throw new UsernameNotFoundException("User not found with username: " + username);
		}
	}
}
Next in the properties configuration file specify the jwt-secret. The secret key is used by the JWT issuer to sign the token's payload, which contains claims about the user or the data being transmitted. The recipient of the JWT can then use the same secret key to verify the signature, ensuring that the token has not been tampered with and can be trusted.
spring.datasource.url= jdbc:mysql://localhost/javainusedb?createDatabaseIfNotExist=true&useSSL=false&allowPublicKeyRetrieval=true
spring.datasource.username= root
spring.datasource.password= root

spring.jpa.properties.hibernate.dialect= org.hibernate.dialect.MySQLDialect
spring.jpa.hibernate.ddl-auto= update

jwt.secret=404E635266556A586E3272357538782F413F4428472B4B6250645367566B5970
The JwtTokenUtil class is a utility class used for generating JSON Web Tokens (JWTs) for user authentication and authorization. It is annotated with @Component to be managed by the Spring framework. The class contains a secret key, which is used to sign the generated JWTs for security purposes. The class provides a method generateToken(UserDetails userDetails), which generates a JWT token with the given user details. It also provides another method generateToken(Map<String, Object> extraClaims, UserDetails userDetails), which allows adding additional claims to the JWT. Both methods internally call a private method buildToken() which constructs the JWT using the specified claims, expiration, and signing key. The signing key is derived from the secret key provided in the application properties file.
package com.javainuse.bootmysqlcrud.config;

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

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

@Component
public class JwtTokenUtil implements Serializable {

	private static final long serialVersionUID = 464214880478737476L;

	@Value("${jwt.secret}")
	private String secret;

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

	public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
		return buildToken(extraClaims, userDetails, 1000000);
	}

	private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
		return Jwts.builder().setClaims(extraClaims).setSubject(userDetails.getUsername())
				.setIssuedAt(new Date(System.currentTimeMillis()))
				.setExpiration(new Date(System.currentTimeMillis() + expiration))
				.signWith(getSignInKey(), SignatureAlgorithm.HS256).compact();
	}

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

}
Next create the JwtRequest class which will be used for incoming user request and will have the fields username and password.
package com.javainuse.bootmysqlcrud.entity;

import java.io.Serializable;

public class JwtRequest implements Serializable {

	private static final long serialVersionUID = 5926468583005150707L;
	
	private String username;
	private String password;
	
	//need default constructor for JSON Parsing
	public JwtRequest(){
	}

	public JwtRequest(String username, String password) {
		this.setUsername(username);
		this.setPassword(password);
	}

	public String getUsername() {
		return this.username;
	}

	public void setUsername(String username) {
		this.username = username;
	}

	public String getPassword() {
		return this.password;
	}

	public void setPassword(String password) {
		this.password = password;
	}
}
We create another entity class named JwtResponse. This will have a single field jwttoken and will be returned back as a response to the user on successful authentication.
package com.javainuse.bootmysqlcrud.entity;

import java.io.Serializable;

public class JwtResponse implements Serializable {

	private static final long serialVersionUID = -8091879091924046844L;
	private final String jwttoken;

	public JwtResponse(String jwttoken) {
		this.jwttoken = jwttoken;
	}

	public String getToken() {
		return this.jwttoken;
	}
}
The JwtAuthenticationController class is a Spring RestController for handling JWT (JSON Web Token) authentication. It is responsible for authenticating a user's credentials and generating a token for authenticated users to access protected resources. The class includes several autowired dependencies, such as an AuthenticationManager, JwtTokenUtil, and UserDetailsService. It has a request mapping for the "/authenticate" endpoint, where it receives a JwtRequest containing the user's authentication credentials. It then calls the authenticate method to validate the credentials, load the user details, and generate a JWT token using the JwtTokenUtil. Finally, it returns a ResponseEntity with the generated token in a JwtResponse object.
package com.javainuse.bootmysqlcrud.controller;

import java.util.Objects;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.DisabledException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestMethod;
import org.springframework.web.bind.annotation.RestController;

import com.javainuse.bootmysqlcrud.config.JwtTokenUtil;
import com.javainuse.bootmysqlcrud.entity.JwtRequest;
import com.javainuse.bootmysqlcrud.entity.JwtResponse;

@RestController
public class JwtAuthenticationController {

	@Autowired
	private AuthenticationManager authenticationManager;

	@Autowired
	private JwtTokenUtil jwtTokenUtil;

	@Autowired
	private UserDetailsService userDetailsService;

	@RequestMapping(value = "/authenticate", method = RequestMethod.POST)
	public ResponseEntity<?> createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {

		authenticate(authenticationRequest.getUsername(), authenticationRequest.getPassword());

		final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());

		final String token = jwtTokenUtil.generateToken(userDetails);

		return ResponseEntity.ok(new JwtResponse(token));
	}

	private void authenticate(String username, String password) throws Exception {
		Objects.requireNonNull(username);
		Objects.requireNonNull(password);

		try {
			authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(username, password));
		} catch (DisabledException e) {
			throw new Exception("USER_DISABLED", e);
		} catch (BadCredentialsException e) {
			throw new Exception("INVALID_CREDENTIALS", e);
		}
	}
}
The WebSecurityConfig class is a configuration class that handles the security configuration for a web application. It uses Spring Security to define authentication and authorization rules.
In this class, a JwtRequestFilter is autowired, which is responsible for authenticating JWT requests. The authenticationManager bean is used to authenticate users based on the provided AuthenticationConfiguration.
The passwordEncoder bean is used to encode passwords for secure storage. The filterChain bean configures the HTTP security settings, disabling CSRF protection and setting the session creation policy to stateless. The authorizeHttpRequests method is used to define authorization rules, allowing all requests to the "/authenticate" endpoint and requiring authentication for all other requests. Finally, the JWT request filter is added before the UsernamePasswordAuthenticationFilter.
package com.javainuse.bootmysqlcrud.config;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class WebSecurityConfig {

	@Autowired
	private JwtRequestFilter jwtRequestFilter;

	@Bean
	public AuthenticationManager authenticationManager(AuthenticationConfiguration authConfig) throws Exception {
		return authConfig.getAuthenticationManager();
	}

	@Bean
	public PasswordEncoder passwordEncoder() {
		return new BCryptPasswordEncoder();
	}

	@Bean
	public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
		http.csrf(csrf -> csrf.disable())
				.sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
				.authorizeHttpRequests(
						auth -> auth.requestMatchers("/authenticate").permitAll().anyRequest().authenticated());
		http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
		return http.build();
	}
}
Let us test the /authenticate url to generate the jwt. In the body send the username and password.
Spring Boot 3 security JWT Generation

Validate JWT

We will be trying to access the GET endpoint /employees which returns us the list of employees from MySQL. Suppose we try to access the GET endpoint /employees, we then get 403 - Forbidden return code.
Spring Boot security JWT 403 forbidden
So we will need to provide the JWT to access the GET endpoint /employees.
If we go to the Online JWT Decoder Tool and check the previously created it JWT we get the following-
JWT online decode
So in the JWT we have passes the username javainuse as a subject claim.
For validating the JWT we will be having following workflow.
Spring Boot 3 security JWT Validation
  1. The user makes a GET request to /employees url. Along with this request, the user also passes the JWT that we created previously.
  2. This request is intercepted by the FilterChainProxy which class the various Filters
  3. One of these filters is the JwtRequestFilter which will retrieve the JWT from the request. It then validates this JWT. If validation is successful then it populates the security context.
  4. Another filter which gets called is the Authorization filter. This filter checks if the previous filters have already authenticated this request or not. Since in our case the authentication is already done by the JwtRequestFilter so this request is forwarded to the controller
  5. The EmployeeController fetches the employee list and returns back the response to the user.
We modify the JwtTokenUtil class to add methods for retrieving various information from a JWT token such as the username, expiration date, and claims.
  1. The getUsername method takes a JWT token as input and returns the username extracted from the token.
  2. The getClaim method takes a JWT token and a Function that extracts a specific claim from the token's payload, returning the result.
  3. The isTokenExpired method checks if a JWT token has expired by comparing the expiration date in the token with the current date.
  4. The getExpiration method returns the expiration date extracted from the JWT token.
  5. The getAllClaims method parses the JWT token, validates it using a signing key, and returns all the claims from the token's payload.
package com.javainuse.bootmysqlcrud.config;

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

import org.springframework.beans.factory.annotation.Value;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;

@Component
public class JwtTokenUtil implements Serializable {

	private static final long serialVersionUID = 464214880478737476L;

	@Value("${jwt.secret}")
	private String secret;

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

	public String generateToken(Map<String, Object> extraClaims, UserDetails userDetails) {
		return buildToken(extraClaims, userDetails, 1000000);
	}

	private String buildToken(Map<String, Object> extraClaims, UserDetails userDetails, long expiration) {
		return Jwts.builder().setClaims(extraClaims).setSubject(userDetails.getUsername())
				.setIssuedAt(new Date(System.currentTimeMillis()))
				.setExpiration(new Date(System.currentTimeMillis() + expiration))
				.signWith(getSignInKey(), SignatureAlgorithm.HS256).compact();
	}

	private Key getSignInKey() {
		byte[] keyBytes = Decoders.BASE64.decode(secret);
		return Keys.hmacShaKeyFor(keyBytes);
	}
	
	public String getUsername(String token) {
		return getClaim(token, Claims::getSubject);
	}
	
	public <T> T getClaim(String token, Function<Claims, T> claimsResolver) {
		final Claims claims = getAllClaims(token);
		return claimsResolver.apply(claims);
	}

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

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

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

	public Boolean validateToken(String token, UserDetails userDetails) {
		final String username = getUsernameFromToken(token);
		return (username.equals(userDetails.getUsername())  && !isTokenExpired(token));
	}

	public String getUsernameFromToken(String token) {
		return getClaimFromToken(token, Claims::getSubject);
	}

	public <T> T getClaimFromToken(String token, Function<Claims, T> claimsResolver) {
		final Claims claims = getAllClaims(token);
		return claimsResolver.apply(claims);
	}
}
Next we modify the JwtRequestFilter class for extracting the JWT token from the request header, validating the token, and setting the authentication in the Spring Security context.
In this class we -
  1. It retrieves the JWT token from the Authorization header of the incoming request.
  2. It extracts the username from the JWT token using JwtTokenUtil class.
  3. It checks if the token is valid and not expired.
  4. It loads the user details from the JwtUserDetailsService based on the username extracted from the token.
  5. If the token is valid, it manually sets the authentication in the Spring Security context using UsernamePasswordAuthenticationToken.
  6. Finally, it proceeds to the next filter in the chain by calling filterChain.doFilter(request, response).
package com.javainuse.bootmysqlcrud.config;

import java.io.IOException;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import com.javainuse.bootmysqlcrud.service.JwtUserDetailsService;

import io.jsonwebtoken.ExpiredJwtException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

	@Autowired
	private JwtUserDetailsService jwtUserDetailsService;

	@Autowired
	private JwtTokenUtil jwtTokenUtil;

	@Override
	protected void doFilterInternal(jakarta.servlet.http.HttpServletRequest request,
			jakarta.servlet.http.HttpServletResponse response, jakarta.servlet.FilterChain filterChain)
			throws jakarta.servlet.ServletException, IOException {

		final String requestTokenHeader = request.getHeader("Authorization");

		String username = null;
		String jwtToken = null;
		// JWT Token is in the form "Bearer token". Remove Bearer word and get only the
		// Token
		if (requestTokenHeader != null && requestTokenHeader.startsWith("Bearer ")) {
			jwtToken = requestTokenHeader.substring(7);
			try {
				username = jwtTokenUtil.getUsernameFromToken(jwtToken);
			} catch (IllegalArgumentException e) {
				System.out.println("Unable to get JWT Token");
			} catch (ExpiredJwtException e) {
				System.out.println("JWT Token has expired");
			}
			
			// Once we get the token validate it.
			if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {

				UserDetails userDetails = this.jwtUserDetailsService.loadUserByUsername(username);

				// if token is valid configure Spring Security to manually set authentication
				if (jwtTokenUtil.validateToken(jwtToken, userDetails)) {

					UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken = new UsernamePasswordAuthenticationToken(
							userDetails, null, userDetails.getAuthorities());
					usernamePasswordAuthenticationToken
							.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
					// After setting the Authentication in the context, we specify
					// that the current user is authenticated. So it passes the Spring Security
					// Configurations successfully.
					SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
				}
			}
		} else {
			logger.warn("JWT Token does not begin with Bearer String");
		}
		
		filterChain.doFilter(request, response);

	}

}


Next using the generated JWT, we send a GET request to /employees to fetch the employees list
Spring Boot 3 security JWT Validation

Download Source Code

Download it -
Spring Boot 3 + JWT Example