JWT-Based CRUD Application in Spring Boot
Overview
This application implements user registration, login, JWT-based authentication, and basic CRUD operations for a sample resource.
Dependencies
Add the following dependencies to your pom.xml:
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<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>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>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
User Entity
Define a User entity for registration and authentication:
import jakarta.persistence.*;
import jakarta.validation.constraints.*;
@Entity
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
@NotNull
@Size(min = 3, max = 50)
private String username;
@NotNull
@Size(min = 8)
private String password;
private String role;
// Getters and Setters
}
Repository
Create a UserRepository for database operations:
import org.springframework.data.jpa.repository.JpaRepository;
import java.util.Optional;
public interface UserRepository extends JpaRepository {
Optional findByUsername(String username);
}
JWT Utility
Implement the JwtUtil class for generating and validating tokens:
import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.stereotype.Component;
import javax.crypto.SecretKey;
import java.util.Date;
@Component
public class JwtUtil {
// The key must be at least 256 bits (32 bytes) long for HS256
private final SecretKey SECRET_KEY = Keys.hmacShaKeyFor("mySecretKey112128828mykeymySecretKey112128828mykey".getBytes()); // 256-bit key
private final int TOKEN_VALIDITY = 3600 * 1000; // 1 hour
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + TOKEN_VALIDITY))
.signWith(SECRET_KEY) // No need to specify algorithm here if it's already defined in the key
.compact();
}
public String extractUsername(String token) {
return Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token)
.getBody()
.getSubject();
}
public boolean validateToken(String token) {
try {
Jwts.parserBuilder()
.setSigningKey(SECRET_KEY)
.build()
.parseClaimsJws(token); // Validates token signature and expiration
return true;
} catch (JwtException | IllegalArgumentException e) {
return false;
}
}
}
Service Layer
Create a UserService for business logic:
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.stereotype.Service;
@Service
public class UserService {
@Autowired
private UserRepository userRepository;
@Autowired
private PasswordEncoder passwordEncoder;
public User registerUser(User user) {
user.setPassword(passwordEncoder.encode(user.getPassword()));
user.setRole("USER");
return userRepository.save(user);
}
public User findByUsername(String username) {
return userRepository.findByUsername(username).orElse(null);
}
}
Authentication Controller
Implement endpoints for registration and login:
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.AuthenticationException;
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthController {
@Autowired
private UserService userService;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private AuthenticationManager authenticationManager;
@PostMapping("/register")
public String registerUser(@RequestBody User user) {
userService.registerUser(user);
return "User registered successfully!";
}
@PostMapping("/login")
public String login(@RequestParam String username, @RequestParam String password) {
try {
Authentication authentication = authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(username, password));
return jwtUtil.generateToken(username);
} catch (AuthenticationException e) {
return "Invalid credentials!";
}
}
}
CRUD Controller
Implement CRUD operations on a sample resource:
import org.springframework.web.bind.annotation.*;
import java.util.List;
@RestController
@RequestMapping("/products")
public class ProductController {
@GetMapping
public List getAllProducts() {
return List.of("Product1", "Product2", "Product3");
}
@PostMapping
public String addProduct(@RequestBody String product) {
return "Product added: " + product;
}
@PutMapping("/{id}")
public String updateProduct(@PathVariable Long id, @RequestBody String product) {
return "Product updated: " + product;
}
@DeleteMapping("/{id}")
public String deleteProduct(@PathVariable Long id) {
return "Product deleted with ID: " + id;
}
}
Security Configuration
Configure Spring Security for JWT-based authentication:
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
public class SecurityConfig {
@Autowired
private UserDetailsService userDetailsService;
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeHttpRequests(auth -> auth
.requestMatchers("/register", "/login").permitAll()
.anyRequest().authenticated()
);
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Bean
public AuthenticationManager authenticationManager(HttpSecurity http) throws Exception {
AuthenticationManagerBuilder authenticationManagerBuilder =
http.getSharedObject(AuthenticationManagerBuilder.class);
authenticationManagerBuilder.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
return authenticationManagerBuilder.build();
}
}
UserDetailsService
This interface is used by Spring Security to load user-specific data, such as username, password, and roles, during the authentication process.
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 CustomUserDetailsService implements UserDetailsService {
private final UserRepository userRepository;
public CustomUserDetailsService(UserRepository userRepository) {
this.userRepository = userRepository;
}
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Find user by username
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found: " + username));
// Return UserDetails implementation
return org.springframework.security.core.userdetails.User.builder()
.username(user.getUsername())
.password(user.getPassword()) // Must be encoded
.roles(user.getRole()) // e.g., "USER", "ADMIN"
.build();
}
}
JWT Filter
The JWT filter intercepts incoming requests to validate the token and authenticate the user.
import java.io.IOException;
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.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import com.ciq.demo.util.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
private final JwtUtil jwtUtil;
private final UserDetailsService userDetailsService;
public JwtRequestFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
this.jwtUtil = jwtUtil;
this.userDetailsService = userDetailsService;
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7);
if (jwtUtil.validateToken(token)) {
String username = jwtUtil.extractUsername(token);
// Set user details here (Authentication object)
System.out.println(username);
// Load user details from the in-memory UserDetailsService
UserDetails userDetails = userDetailsService.loadUserByUsername(username);
// Set the authentication in the context
UsernamePasswordAuthenticationToken authentication = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
SecurityContextHolder.getContext().setAuthentication(authentication);
}
}
chain.doFilter(request, response);
}
}
Testing
- Register a user with
POST /register. - Login to get a JWT token with
POST /login. - Use the token in the Authorization header to access
/productsendpoints.