Spring Boot 3 and JWT Application
Implementing a Spring Boot 3 application with JWT (JSON Web Token) for authentication involves several key steps. This guide will walk you through the process step by step. It assumes that you have a basic understanding of Spring Boot, Spring Security, and JWT concepts.
Authentication Flow Diagram
This diagram represents the flow of how a user authenticates using a username and password and how the JWT is generated and validated.
2. Request Filtering with JWT
This diagram illustrates how the JWT is extracted from the request and validated via a filter before accessing protected resources.
3. Class Diagram of Key Components
This diagram shows the main classes and their relationships within the application.
4. Sequence Diagram for Token Validation
This sequence diagram shows the process of validating a JWT when a user tries to access a protected resource.
5. Database Interaction Diagram
This diagram shows how the UserDetailsService interacts with the database (via UserRepository) during the authentication process.
Summary of UML Diagrams:
These diagrams offer a visual understanding of the various components and interactions involved in the Spring Boot 3 JWT implementation. You can paste these diagrams into a .puml file or use an online PlantUML editor to render them.
1. Set Up the Spring Boot 3 Project
2. Configure Maven Dependencies for JWT
Edit the pom.xml file to include dependencies for JWT and any additional dependencies you may need.
xml
<dependencies>
<!-- Spring Boot Starter Web -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Spring Boot Starter Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- JWT Auth Dependency -->
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version>
</dependency>
<!-- Spring Boot Starter Data JPA -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<!-- H2 Database for Development -->
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<scope>provided</scope>
</dependency>
</dependencies>
3. Create the User Entity
Define a User entity that will hold the user information and roles. You can also define Role if necessary for RBAC (Role-Based Access Control).
java
@Entity
@Data
@NoArgsConstructor
@AllArgsConstructor
public class User {
@Id
@GeneratedValue(strategy = GenerationType.IDENTITY)
private Long id;
private String username;
private String password;
private String roles; // For simplicity, you can store roles as a comma-separated string.
// Add constructors, getters, setters, etc.
}
4. Create a UserRepository
Create a repository interface for database interactions:
java
@Repository
public interface UserRepository extends JpaRepository<User, Long> {
Optional<User> findByUsername(String username);
}
5. Create a JWT Utility Class
This class will handle the generation and validation of JWT tokens.
java
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;
import java.util.function.Function;
@Component
public class JwtUtil {
private String SECRET_KEY = "mysecret"; // Use environment variables for production!
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.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
}
private Boolean isTokenExpired(String token) {
return extractExpiration(token).before(new Date());
}
public String generateToken(UserDetails userDetails) {
Map<String, Object> claims = new HashMap<>();
return createToken(claims, userDetails.getUsername());
}
private String createToken(Map<String, Object> claims, String subject) {
return Jwts.builder().setClaims(claims).setSubject(subject).setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 60 60 * 10))
.signWith(SignatureAlgorithm.HS256, SECRET_KEY).compact();
}
public Boolean validateToken(String token, UserDetails userDetails) {
final String username = extractUsername(token);
return (username.equals(userDetails.getUsername()) && !isTokenExpired(token));
}
}
6. Create Custom UserDetailsService
This service will fetch the user data from the database for authentication purposes.
java
@Service
public class MyUserDetailsService implements UserDetailsService {
@Autowired
private UserRepository userRepository;
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
User user = userRepository.findByUsername(username)
.orElseThrow(() -> new UsernameNotFoundException("User not found"));
return new org.springframework.security.core.userdetails.User(user.getUsername(), user.getPassword(),
new ArrayList<>());
}
}
7. Create a JWT Request Filter
This filter will intercept each request, check for a JWT in the header, and authenticate the user if the token is valid.
Recommended by LinkedIn
java
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private MyUserDetailsService userDetailsService;
@Autowired
private JwtUtil jwtUtil;
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain chain)
throws ServletException, IOException {
final String authorizationHeader = request.getHeader("Authorization");
String username = null;
String jwt = null;
if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
jwt = authorizationHeader.substring(7);
username = jwtUtil.extractUsername(jwt);
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtUtil.validateToken(jwt, userDetails)) {
UsernamePasswordAuthenticationToken authenticationToken = new UsernamePasswordAuthenticationToken(
userDetails, null, userDetails.getAuthorities());
authenticationToken.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(authenticationToken);
}
}
chain.doFilter(request, response);
}
}
8. Configure Spring Security (continued)
Update the Spring Security configuration to allow JWT-based authentication and authorize requests accordingly:
java
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {
@Autowired
private MyUserDetailsService myUserDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(myUserDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/authenticate").permitAll() // Allow access to authenticate endpoint
.anyRequest().authenticated() // All other endpoints are secured
.and()
.sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);
// Add the JWT filter before processing requests
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
}
9. Create Authentication Controller
Create a controller that exposes an endpoint to authenticate users and return a JWT.
java
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtUtil jwtUtil;
@Autowired
private MyUserDetailsService userDetailsService;
@PostMapping("/authenticate")
public ResponseEntity<?> createAuthenticationToken(@RequestBody AuthenticationRequest authRequest) throws Exception {
try {
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword())
);
} catch (BadCredentialsException e) {
throw new Exception("Incorrect username or password", e);
}
final UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername());
final String jwt = jwtUtil.generateToken(userDetails);
return ResponseEntity.ok(new AuthenticationResponse(jwt));
}
}
10. Create AuthenticationRequest and AuthenticationResponse Models
These classes are used to handle the incoming request for authentication and to return the JWT token as a response.
java
public class AuthenticationRequest {
private String username;
private String password;
// Getters and setters
}
java
public class AuthenticationResponse {
private final String jwt;
public AuthenticationResponse(String jwt) {
this.jwt = jwt;
}
public String getJwt() {
return jwt;
}
}
11. Application Properties Configuration
Add any necessary configuration in the application.properties file, such as setting up your database connection:
properties
# H2 Database (for dev)
spring.h2.console.enabled=true
spring.datasource.url=jdbc:h2:mem:testdb
spring.datasource.driverClassName=org.h2.Driver
spring.datasource.username=sa
spring.datasource.password=password
spring.jpa.hibernate.ddl-auto=update
# Secret key for JWT can be externalized here as well:
jwt.secret=mysecret
12. Testing the Application
1. Run the Spring Boot Application:
Run the Spring Boot application, ensuring that all components (like the database and security) are correctly set up.
2. Test the Authentication:
bash
POST /authenticate
Content-Type: application/json
{
"username": "your-username",
"password": "your-password"
}
json
{
"jwt": "eyJhbGciOiJIUzI1NiIsInR5cCI..."
}
3. Access Protected Endpoints:
Now, use the JWT token in the Authorization header (as Bearer token) to access protected endpoints:
bash
GET /some-protected-endpoint
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI...
13. Finalizing
Summary of Steps:
With these steps, you have successfully implemented a Spring Boot 3 application with JWT authentication!
spring
Senior Solutions Architect
2moHi All, thanks for visiting my article.