JWT Authentication, Spring Security 6 and Spring Boot 3 and with Simple Project.

 In this article we will learn about JWT based authentication and authorization with login feature.


Prerequisites

  1. Good Knowledge of Java.
  2. Basic Understanding of Spring Boot.
  3. Basic Understanding of Spring Security (If you don’t know how to work with Spring Security, first look at my Spring Security 6 tutorial. — https://ehashtechhub.blogspot.com/2025/08/spring-security-6-and-spring-boot-3.html 
  4. Basic Understanding of MySQL workbench and IDE (In this project I have used Intellij IDE).

Required Technologies and Apps

  1. Java (latest jdk)
  2. Spring Boot project file.
  3. Intellij IDE
  4. Postman
  5. MySQL Workbench

Get source code from GitHub —  Download Source Code

JWT (JSON Web Token) Overview

JWT stands for JSON Web Token, which is an open standard for securely transmitting information as a JSON object between parties, typically a client and a server.



Lets start

01  Generate Spring BootProject File from Spring Initializer Tool —

Spring Initializr



Click generate button and download the zip file. After, open the file from Intellij IDE.

02 — Set up the file structure and add the required classes and interfaces.




03 — Establish the database Connection. (application.properties)

spring.datasource.url = jdbc:mysql://localhost:3306/security_db?createDatabaseIfNotExist=true
spring.datasource.username = root
spring.datasource.password = Online12@
#spring.datasource.diver-class-name =com.mysql.cj.jdbc.Driver


# Hibernate properties
spring.jpa.properties.hibernate.dialect=org.hibernate.dialect.MySQLDialect

# create, create-drop , update
spring.jpa.hibernate.ddl-auto = update

04 — Add required Model classes, Repositories and DTO classes.

User Model

import jakarta.persistence.*;
import lombok.Data;
import java.util.Set;

@Entity
@Data
@Table(name = "users")
public class User {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
@Column(nullable = false, unique = true)
private String username;
@Column(nullable = false, unique = true)
private String email;
@Column(nullable = false)
private String password;

@ManyToMany(fetch = FetchType.EAGER)
@JoinTable(name = "users_roles",
joinColumns = @JoinColumn(name = "user_id", referencedColumnName = "id"),
inverseJoinColumns = @JoinColumn(name = "role_id", referencedColumnName = "id")
)

private Set<Role> roles;
}

Role Model

import jakarta.persistence.*;
import lombok.Data;

@Entity
@Data
@Table(name = "roles")
public class Role {

@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String name;
}

User Repository and Role Repository

public interface UserRepository extends JpaRepository<User,Long> {

Optional<User> findByUsername(String username);
}

-------------------------------------------------------------------
public interface RoleRepository extends JpaRepository<Role,Long> {
}

LoginDto class and AuthResponseDto class

@Data
public class LoginDto {

private String username;
private String password;

}
--------------------------------------------------------

@Data
public class AuthResponseDto {

private String accessToken;

}

05 — Add Controller classes.

AuthController for taking JWT token from the login API.

import jwtSecurity.example.jwtDemo.Dto.AuthResponseDto;
import jwtSecurity.example.jwtDemo.Dto.LoginDto;
import jwtSecurity.example.jwtDemo.Service.AuthService;
import lombok.AllArgsConstructor;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@AllArgsConstructor
@RestController
@RequestMapping("/api/auth")
public class AuthController {

@Autowired
private AuthService authService;

// Build Login REST API
@PostMapping("/login")
public ResponseEntity<AuthResponseDto> login(@RequestBody LoginDto loginDto){

//01 - Receive the token from AuthService
String token = authService.login(loginDto);

//02 - Set the token as a response using JwtAuthResponse Dto class
AuthResponseDto authResponseDto = new AuthResponseDto();
authResponseDto.setAccessToken(token);

//03 - Return the response to the user
return new ResponseEntity<>(authResponseDto, HttpStatus.OK);
}
}

SimpleController class for testing authenticated APIs.

import org.springframework.http.ResponseEntity;
import org.springframework.security.access.prepost.PreAuthorize;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RestController;

@RestController
@RequestMapping("/api/")
public class SimpleController {

@PreAuthorize("hasRole('ADMIN')")
@GetMapping("/admin")
public ResponseEntity<String> helloAdmin(){
return ResponseEntity.ok("Hello Admin");
}

@PreAuthorize("hasRole('USER')")
@GetMapping("/user")
public ResponseEntity<String> helloUser(){
return ResponseEntity.ok("Hello User");
}
}

06 — Add Service classes

AuthService Interface

public interface AuthService {
String login(LoginDto loginDto);
}

AuthServiceImpl class

import jwtSecurity.example.jwtDemo.Config.JwtTokenProvider;
import jwtSecurity.example.jwtDemo.Dto.LoginDto;
import jwtSecurity.example.jwtDemo.Service.AuthService;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.stereotype.Service;

@Service
public class AuthServiceImpl implements AuthService {

@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenProvider jwtTokenProvider;

@Override
public String login(LoginDto loginDto) {

// 01 - AuthenticationManager is used to authenticate the user
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(
loginDto.getUsername(),
loginDto.getPassword()
));

/* 02 - SecurityContextHolder is used to allows the rest of the application to know
that the user is authenticated and can use user data from Authentication object */

SecurityContextHolder.getContext().setAuthentication(authentication);

// 03 - Generate the token based on username and secret key
String token = jwtTokenProvider.generateToken(authentication);

// 04 - Return the token to controller
return token;
}
}

07 — Add security and JWT configuration classes.

First, you have to add some dependencies to your pom.xml file. Then reload the project (Right click pom.xml -> Mavan -> Reload)

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

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

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

SecurityConfig class

import lombok.AllArgsConstructor;
import org.springframework.context.annotation.Bean;
import org.springframework.http.HttpMethod;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.Customizer;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
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;
import org.springframework.stereotype.Component;

@Component
@EnableMethodSecurity
@AllArgsConstructor
public class SecurityConfig {

private UserDetailsService userDetailsService;

private JwtAuthenticationEntryPoint authenticationEntryPoint;

private JwtAuthenticationFilter authenticationFilter;

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

@Bean
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {

http.csrf(csrf -> csrf.disable())
.authorizeHttpRequests((authorize) -> {
authorize.requestMatchers("/api/auth/**").permitAll();
authorize.requestMatchers(HttpMethod.OPTIONS, "/**").permitAll();
authorize.anyRequest().authenticated();
}).httpBasic(Customizer.withDefaults());

http.exceptionHandling( exception -> exception
.authenticationEntryPoint(authenticationEntryPoint));

http.addFilterBefore(authenticationFilter, UsernamePasswordAuthenticationFilter.class);

return http.build();
}

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

CustomUserDetailsService Class

import jwtSecurity.example.jwtDemo.Model.User;
import jwtSecurity.example.jwtDemo.Repository.UserRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
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.Component;

import java.util.Set;
import java.util.stream.Collectors;

@Component
public class CustomUserDetailsService implements UserDetailsService {

@Autowired
private UserRepository userRepository;

@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {

User user = userRepository.findByUsername(username) .orElseThrow(() ->
new UsernameNotFoundException("User not exists by Username or Email"));

Set<GrantedAuthority> authorities = user.getRoles().stream()
.map((role) -> new SimpleGrantedAuthority(role.getName()))
.collect(Collectors.toSet());

return new org.springframework.security.core.userdetails.User(
username,
user.getPassword(),
authorities
);
}
}

JwtTokenProvider class

import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.Authentication;
import org.springframework.stereotype.Component;

import javax.crypto.SecretKey;
import java.security.Key;
import java.util.Date;

@Component
public class JwtTokenProvider {

private String jwtSecret = "af60addca9ea3e3c099551e1b6576c9966dce0a33de879dd7e160f86dbd872ca236d6e9ee66fb6e30039fe7c345324a10f3d0741b0600fa7a45df4c6691eff4f4209767ed39f51e37717d8feecd5dd14fc34ebe619e6a29ae91d9ffe134cb5718bec0b3680d6ae7fc09e67763fe7c05d05d3ba69f47211163852633755b7f861132b0c98f8d7c1af9152d547408e676867a0a32fb525a4354180f5fb6b2dc23b5faa4155b8db63385f96259a90b6ee0e74a5b90a4f0f4fa96fafc296c64588b5c009b3829ae2e1d69a1cf7569b50a65fa553350495d18816f785f961c970c0a9cb9c8da25cc5e9fa4a3e9527a132d616b232d1ee21c3bf6dc8d9e3376e2e82c0";
private long jwtExpirationDate = 3600000; //1h = 3600s and 3600*1000 = 3600000 milliseconds

public String generateToken(Authentication authentication) {

String username = authentication.getName();
Date currentDate = new Date();
Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);

String token = Jwts.builder()
.subject(username)
.issuedAt(new Date())
.expiration(expireDate)
.signWith(key(), SignatureAlgorithm.HS256)
.compact();

return token;
}

private Key key(){
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
}

// extract username from JWT token
public String getUsername(String token){

return Jwts.parser()
.verifyWith((SecretKey) key())
.build()
.parseSignedClaims(token)
.getPayload()
.getSubject();
}

// validate JWT token
public boolean validateToken(String token){
Jwts.parser()
.verifyWith((SecretKey) key())
.build()
.parse(token);
return true;

}
}

You can get secret key from this website (https://randomkeygen.com/)



Important

In general, the best practice for generating secret keys is to create them yourself using secure random number generation methods. Then you can improve security and trust.

In order to generate secret key you need to add generateSecretKey() method and assign it to jwtSecret.

( But I have used secret key from the above web site for this project. you can you this way if you want)

import java.security.SecureRandom;
import java.util.Base64;

@Component
public class JwtTokenProvider {

private String jwtSecret = generateSecretKey();
private long jwtExpirationDate = 3600000; //1h = 3600s and 3600*1000 = 3600000 milliseconds

public String generateToken(Authentication authentication) {

String username = authentication.getName();
Date currentDate = new Date();
Date expireDate = new Date(currentDate.getTime() + jwtExpirationDate);

String token = Jwts.builder()
.subject(username)
.issuedAt(new Date())
.expiration(expireDate)
.signWith(key(), SignatureAlgorithm.HS256)
.compact();

return token;
}

private Key key(){
return Keys.hmacShaKeyFor(Decoders.BASE64.decode(jwtSecret));
}



public String generateSecretKey() {
// length means (32 bytes are required for 256-bit key)
int length = 32;

// Create a secure random generator
SecureRandom secureRandom = new SecureRandom();

// Create a byte array to hold the random bytes
byte[] keyBytes = new byte[length];

// Generate the random bytes
secureRandom.nextBytes(keyBytes);

// Encode the key in Base64 format for easier storage and usage
return Base64.getEncoder().encodeToString(keyBytes);
}

.....
......
.....


}

JwtAuthenticationFIlter class

This filter is responsible for checking and validating the JWT on each request that requires authentication.

It extends the OncePerRequestFilter class, which ensures that the doFilterInternal method is only invoked once per request.

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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.core.userdetails.UserDetailsService;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

@Autowired
private JwtTokenProvider jwtTokenProvider;

private UserDetailsService userDetailsService;

//Constructor
public JwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider, UserDetailsService userDetailsService) {
this.jwtTokenProvider = jwtTokenProvider;
this.userDetailsService = userDetailsService;
}


// This method is executed for every request intercepted by the filter.
//And, it extract the token from the request header and validate the token.
@Override
protected void doFilterInternal(HttpServletRequest request,
HttpServletResponse response,
FilterChain filterChain)
throws ServletException, IOException {

// Get JWT token from HTTP request
String token = getTokenFromRequest(request);

// Validate Token
if(StringUtils.hasText(token) && jwtTokenProvider.validateToken(token)){
// get username from token
String username = jwtTokenProvider.getUsername(token);

UserDetails userDetails = userDetailsService.loadUserByUsername(username);

UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails,
null,
userDetails.getAuthorities()
);

authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));

SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}

filterChain.doFilter(request, response);
}

// Extract the token
private String getTokenFromRequest(HttpServletRequest request){
String bearerToken = request.getHeader("Authorization");

if(StringUtils.hasText(bearerToken) && bearerToken.startsWith("Bearer ")){
return bearerToken.substring(7, bearerToken.length());
}

return null;
}
}

JwtAuthenticationEntryPoint class

It serves as the starting point for handling authentication errors related to JWT-based authentication.

import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.stereotype.Component;

import java.io.IOException;

@Component
public class JwtAuthenticationEntryPoint implements AuthenticationEntryPoint {


@Override
public void commence(
HttpServletRequest request,
HttpServletResponse response,
AuthenticationException authException)
throws IOException, ServletException {

response.sendError(HttpServletResponse.SC_UNAUTHORIZED, authException.getMessage());

}
}

08 — Run and test the application

Add data to the database.


You can generate password from this website. (https://bcrypt-generator.com/)


Test the Application using Postman.



Lakshitha is a user. So, he can access only user api.

Summary

In this artical we establisted JWT token based security with Spring Boot Security.

Post a Comment

Previous Post Next Post