Implementing Simple JWT Authentication with JPA in Spring Boot 3.2.5

Seif Ibrahim
10 min readApr 22, 2024

--

Introduction:

Securing web applications is a paramount concern in today’s digital landscape, and JSON Web Tokens (JWT) have emerged as a popular choice for implementing authentication mechanisms due to their simplicity and scalability. In this tutorial, we will explore how to integrate JWT authentication into a Spring Boot 3.2.5 and Spring Security 6.2.4 application. By leveraging the power of JWT, we can ensure secure communication between clients and servers without the need for sessions or cookies.

Prerequisites:

Before diving into the implementation, ensure you have the following prerequisites:

  • Basic understanding of Spring Boot framework.
  • Familiarity with RESTful API development.
  • Java Development Kit (JDK) installed on your machine.
  • Maven installed for dependency management.

Now, let’s proceed with the implementation steps.

Dependencies:

Let’s take a moment to understand the dependencies we’ll be using. Ensure that you have the following dependencies configured in your `pom.xml` file:

<dependencies>
<!-- Spring Boot Starter 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>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- Spring Boot Starter Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok (Optional, for cleaner code) -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- JWT Dependencies -->
<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>
</dependencies>

These dependencies include Spring Boot starters for web, data JPA, testing, security, and validation. Additionally, we include the Lombok library for cleaner code (optional) and the jjwt library for JWT support. With these dependencies in place, we are ready to proceed with implementing JWT authentication in our Spring Boot application.

Folder Structure:

Now let’s ensure that our project follows a well-organized folder structure. A clean and structured project layout makes it easier to manage code and resources. Here’s the folder structure for our Spring Boot project:

Step 1: Create Entity Classes

In this step, we’ll create two entity classes: AuthRequest and UserInfo.

AuthRequest.java:

package com.example.demo.Entities;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Data
@AllArgsConstructor
@NoArgsConstructor
public class AuthRequest {
private String username;
private String password;
}

This class represents the request payload for user authentication. It contains fields for username and password.

UserInfo.java:

package com.example.demo.Entities;
import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.persistence.Id;
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
@Entity
@Data
@AllArgsConstructor
@NoArgsConstructor
public class UserInfo {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private int id;
private String name;
private String email;
private String password;
private String roles;
}

This class represents user information stored in the database. It includes fields such as name, email, password, and roles. The @Entity annotation indicates that this class is a JPA entity, and the @Id annotation marks the primary key field.

With these entity classes in place, we can now proceed to implement the repository and service layers for managing user data.

Step 2: Implement Repository Interface

Let’s create a repository interface for interacting with the database.

package com.example.demo.Repositories;
import org.springframework.data.jpa.repository.JpaRepository;
import org.springframework.stereotype.Repository;
import com.example.demo.Entities.UserInfo;
import java.util.Optional;

@Repository
public interface UserInfoRepository extends JpaRepository<UserInfo, Integer> {
Optional<UserInfo> findByName(String username);
}

This repository interface extends JpaRepository and provides methods for CRUD operations on the UserInfo entity. The findByName method is used to retrieve user information by username.

With the repository interface defined, we can now move on to implement the service layer for managing user data.

Step 3: Implement User Service

Now, let’s create a service class to manage user-related operations, including loading user details by username and adding users to the database.

package com.example.demo.Services;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
import com.example.demo.Entities.UserInfo;
import com.example.demo.Repositories.UserInfoRepository;
import java.util.Optional;
@Service
public class UserInfoService implements UserDetailsService {
private final UserInfoRepository repository;
private final PasswordEncoder encoder;
public UserInfoService(UserInfoRepository repository, PasswordEncoder encoder) {
this.repository = repository;
this.encoder = encoder;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
Optional<UserInfo> userDetail = repository.findByName(username);
// Converting userDetail to UserDetails
return userDetail.map(UserInfoDetails::new)
.orElseThrow(() -> new UsernameNotFoundException("User not found " + username));
}
public String addUser(UserInfo userInfo) {
userInfo.setPassword(encoder.encode(userInfo.getPassword()));
repository.save(userInfo);
return "User Added Successfully";
}

}

This service class implements the UserDetailsService interface provided by Spring Security. It overrides the loadUserByUsername method to load user details from the database based on the username. If the user is not found, it throws a UsernameNotFoundException.

Additionally, it provides a method addUser to add users to the database, encrypting their passwords using the PasswordEncoder.

Step 4: Implement User Details

Now, let’s create a class to represent user details fetched from the database and provide them to Spring Security.

package com.example.demo.Services;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.UserDetails;
import com.example.demo.Entities.UserInfo;
import java.util.Arrays;
import java.util.Collection;
import java.util.List;
import java.util.stream.Collectors;
public class UserInfoDetails implements UserDetails {
private String name;
private String password;
private List<GrantedAuthority> authorities;
public UserInfoDetails(UserInfo userInfo) {
name = userInfo.getName();
password = userInfo.getPassword();
authorities = Arrays.stream(userInfo.getRoles().split(","))
.map(SimpleGrantedAuthority::new)
.collect(Collectors.toList());
}
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
return authorities;
}
@Override
public String getPassword() {
return password;
}
@Override
public String getUsername() {
return name;
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
}

Step 5: Implement JWT Service

Let’s create a service class responsible for generating and validating JWT tokens.

package com.example.demo.Services;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import io.jsonwebtoken.io.Decoders;
import io.jsonwebtoken.security.Keys;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;

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

@Component
public class JwtService {
public static final String SECRET = "9D0EB6B1C2E1FAD0F53A248F6C3B5E4E2F6D8G3H1I0J7K4L1M9N2O3P5Q0R7S9T1U4V2W6X0Y3Z";
public String generateToken(String userName) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userName);
}
private String createToken(Map<String, Object> claims, String userName) {
return Jwts.builder()
.setClaims(claims)
.setSubject(userName)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 30))
.signWith(getSignKey(), SignatureAlgorithm.HS256).compact();
}
private Key getSignKey() {
byte[] keyBytes= Decoders.BASE64.decode(SECRET);
return Keys.hmacShaKeyFor(keyBytes);
}
public String extractUsername(String token) {
return extractClaim(token, Claims::getSubject);
}
public Date extractExpiration(String token) {
return extractClaim(token, Claims::getExpiration);
}
public <T> T extractClaim(String token, Function<Claims, T> claimsResolver) {
final Claims claims = extractAllClaims(token);
return claimsResolver.apply(claims);
}
private Claims extractAllClaims(String token) {
return Jwts
.parserBuilder()
.setSigningKey(getSignKey())
.build()
.parseClaimsJws(token)
.getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}

This service class provides methods for generating JWT tokens, extracting information from tokens, and validating tokens against user details. The secret key used for token signing is provided as a static field, which you can modify as needed for security purposes.

With the JWT service implemented, we have completed the necessary components for JWT authentication in our Spring Boot application. Next, we’ll integrate these components into Spring Security by configuring a JWT authentication filter.

Step 6: Implement JWT Authentication Filter

Now, let’s create a JWT authentication filter to intercept incoming requests, extract JWT tokens, and validate them against user details.

package com.example.demo.filter;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
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 java.io.IOException;
import com.example.demo.Services.JwtService;
import com.example.demo.Services.UserInfoService;

@Component
public class JwtAuthFilter extends OncePerRequestFilter {
private final JwtService jwtService;
private final UserInfoService userDetailsService;
JwtAuthFilter(JwtService jwtService, UserInfoService userDetailsService) {
this.jwtService = jwtService;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
String authHeader = request.getHeader("Authorization");
String token = null;
String username = null;
if (authHeader != null && authHeader.startsWith("Bearer ")) {
token = authHeader.substring(7);
username = jwtService.extractUsername(token);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
if (jwtService.validateToken(token, userDetails)) {
UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
authToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authToken);
}
}
filterChain.doFilter(request, response);
}
}

This filter extends OncePerRequestFilter, ensuring that it’s invoked only once per request. It intercepts incoming requests, extracts the JWT token from the Authorization header, and validates it against user details fetched from the database. If the token is valid, it sets the authentication in the SecurityContextHolder. Finally, it passes the request down the filter chain for further processing.

With the JWT authentication filter in place, our Spring Boot application is now capable of intercepting requests and authenticating users based on JWT tokens.

Step 7: Implement User Controller

Now, let’s create a controller to handle user-related endpoints such as user registration and authentication token generation.

package com.example.demo.Controllers;

import org.springframework.http.HttpStatus;
import org.springframework.http.ResponseEntity;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.web.bind.annotation.*;
import com.example.demo.Entities.AuthRequest;
import com.example.demo.Entities.UserInfo;
import com.example.demo.Services.JwtService;
import com.example.demo.Services.UserInfoService;

@RestController
@RequestMapping("/auth")
public class UserController {
private final UserInfoService service;
private final JwtService jwtService;
private final AuthenticationManager authenticationManager;
UserController(UserInfoService service, JwtService jwtService, AuthenticationManager authenticationManager) {
this.service = service;
this.jwtService = jwtService;
this.authenticationManager = authenticationManager;
}

@PostMapping("/register")
public ResponseEntity<String> addNewUser(@RequestBody UserInfo userInfo) {
String response = service.addUser(userInfo);
return ResponseEntity.status(HttpStatus.CREATED).body(response);
}

@PostMapping("/generateToken")
public ResponseEntity<String> authenticateAndGetToken(@RequestBody AuthRequest authRequest) {
Authentication authentication = authenticationManager.authenticate(new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()));
if (authentication.isAuthenticated()) {
String token = jwtService.generateToken(authRequest.getUsername());
return ResponseEntity.ok(token);
} else {
throw new UsernameNotFoundException("Invalid user request!");
}
}
@GetMapping("/hello")
public String hello() {
return "Hello World!";
}

}

This controller class provides endpoints for user registration and authentication token generation. The addNewUser method handles the registration of new users, while the authenticateAndGetToken method authenticates users using Spring Security’s AuthenticationManager and generates a JWT token using the JwtService.

Additionally, a simple hello endpoint is included for testing purposes.

With the user controller implemented, our Spring Boot application is now capable of registering new users and generating authentication tokens.

Step 8: Configure Security

In this step, we’ll configure Spring Security to handle authentication and authorization, including integrating our JWT authentication filter, defining security rules, and setting up authentication providers.

package com.example.demo.config; 
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationProvider;
import org.springframework.security.authentication.dao.DaoAuthenticationProvider;
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.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import com.example.demo.Repositories.UserInfoRepository;
import com.example.demo.Services.UserInfoService;
import com.example.demo.filter.JwtAuthFilter;
import static org.springframework.security.config.Customizer.withDefaults;
@Configuration
@EnableWebSecurity
@EnableMethodSecurity
public class SecurityConfig {
private final JwtAuthFilter authFilter;
public SecurityConfig(JwtAuthFilter authFilter) {
this.authFilter = authFilter;
}
// User Creation
@Bean
public UserDetailsService userDetailsService(UserInfoRepository repository, PasswordEncoder passwordEncoder) {
return new UserInfoService(repository, passwordEncoder);
}
@Bean
public SecurityFilterChain filterChain(HttpSecurity http, AuthenticationProvider authenticationProvider) throws Exception {
return http
.authorizeHttpRequests((authz) -> authz
.requestMatchers("/auth/generateToken", "/auth/register").permitAll()
.requestMatchers("/auth/hello").authenticated()
)
.httpBasic(withDefaults()).csrf((csrf) -> csrf.disable())
.sessionManagement((session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
.authenticationProvider(authenticationProvider)
.addFilterBefore(authFilter, UsernamePasswordAuthenticationFilter.class)
.build();
}
@Bean
public AuthenticationProvider authenticationProvider(UserDetailsService userDetailsService, PasswordEncoder passwordEncoder) {
DaoAuthenticationProvider authenticationProvider = new DaoAuthenticationProvider();
authenticationProvider.setUserDetailsService(userDetailsService);
authenticationProvider.setPasswordEncoder(passwordEncoder);
return authenticationProvider;
}
@Bean
public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception {
return config.getAuthenticationManager();
}
}

This configuration class sets up Spring Security for our application.

It defines beans for the service responsible for user details retrieval, Configures security rules, specifying which endpoints are accessible to which users and enabling stateless session management, Configures an authentication provider, specifying how authentication should be performed and Configures an authentication manager, which is used by the filter to authenticate users.

With the security configuration in place, our Spring Boot application is now secured and ready to authenticate users using JWT tokens.

Step 9: Configure Password Encoder

In this step, we’ll configure a password encoder bean to securely hash passwords before storing them in the database.

package com.example.demo.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
@Configuration
public class PasswordEncoderConfig {
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}

This configuration class defines a bean for the BCryptPasswordEncoder, which is a popular password encoder provided by Spring Security. The encoder is used to securely hash passwords before storing them in the database during user registration.

With the password encoder configured, our application is now equipped with a secure mechanism to hash user passwords, enhancing the security of user authentication.

Step 10: Configure Application Properties

Now, let’s configure the application properties to set up the database connection and other essential settings for our Spring Boot application. In the src/main/resources/ directory add the following properties to the application.properties file:

spring.application.name=demo
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.database-platform=org.hibernate.dialect.H2Dialect

These properties configure our application to use an H2 in-memory database named “testdb” with the provided username and password. Additionally, they set up Hibernate to use the appropriate dialect for working with the H2 database.

With the application properties configured, our Spring Boot application is ready to establish a database connection and execute database operations according to our defined settings.

Step 11: Test Endpoints with Postman

To verify that our authentication system is working as expected, we’ll use Postman to make requests to our API endpoints. Follow these steps to test the endpoints:

Register a New User:

Use the Postman request builder to send a POST request to the /auth/register endpoint with the user information in the request body.

Ensure that you include the user details (e.g., username, email, password) as shown below:

Generate Authentication Token:

Send a POST request to the /auth/generateToken endpoint with the user credentials in the request body (username and password).

Upon successful authentication, the response will contain the JWT token in the response body. Copy this token for subsequent requests.

Access Protected Endpoint:

Make a GET request to the /auth/hello endpoint with the JWT token included in the Authorization header as a Bearer token.

Ensure that you set the Authorization header with the format Bearer <token>, where <token> is the JWT token obtained from the previous step.

This request should return a “Hello World!” message, indicating that the endpoint is accessible with valid authentication.

By following these steps and using Postman to interact with our API endpoints, we can confirm that our JWT-based authentication system is functioning correctly. This testing process helps ensure the security and reliability of our application’s authentication mechanism.

Conclusion

In this article, we’ve explored how to implement simple JWT authentication in a Spring Boot application. You’ve learned how to set up a basic JWT authentication system in a Spring Boot application. This authentication mechanism can be further enhanced and customized to meet the security requirements of your application. Remember to always follow best practices for secure authentication and protect sensitive information when building production-ready applications.

--

--