Today we'll do a hands-on integration of JWT into our app. We'll add token generation and validation, set up security filters, and secure a REST API. To do that we'll build a small Spring Boot app where authentication is handled via JWT.
JWT isn't just a trendy buzzword. It's a convenient, lightweight, and widely used authentication mechanism in modern web apps. For example, if you're a developer going to an interview, be sure they'll ask you to "talk about JWT". And if they don't (lucky you!), in real projects you'll run into it whenever authentication is involved.
Plan of our application
Our app will have two main features:
- Authentication — the user sends their credentials (username and password), and the server returns a JWT.
- Access to protected resources — the user sends the JWT in the request header, and if the token is valid the server grants access to the resource.
Step 1. Preparation: project configuration
First, create a Spring Boot project. We'll use:
- Maven for dependency management.
- Spring Boot version 2.7+.
- Dependencies:
spring-boot-starter-security,spring-boot-starter-web,jjwt(Java JWT library).
Maven configuration Create the project using Spring Initializr (https://start.spring.io/). Add the following dependencies to pom.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>
<!-- Java JWT library -->
<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>
Step 2. SecurityConfig setup
We need to configure security and remove the default Spring Security login form. We'll replace it with JWT-based authentication.
Create the SecurityConfig class:
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll() // Access to these routes without authorization
.anyRequest().authenticated() // All other routes require authorization
.and()
.httpBasic();
return http.build();
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
}
We disabled CSRF (only for simplicity in this example), allowed access to /api/auth/** endpoints without authentication, and required authentication for all other requests.
Step 3. Creating the user entity
We need a user entity to simulate authentication. For simplicity we'll store users in memory.
public class User {
private String username;
private String password;
// Constructors, getters and setters
public User(String username, String password) {
this.username = username;
this.password = password;
}
public String getUsername() {
return username;
}
public String getPassword() {
return password;
}
}
Step 4. Creating a service to generate JWT
Now let's implement a service that will generate JWTs.
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Service;
import java.util.Date;
@Service
public class JwtService {
private final String SECRET_KEY = "your-256-bit-secret"; // Never store secrets in code in real projects
public String generateToken(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date())
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 30)) // 30 minutes
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
}
Step 5. Authentication controller
Create a controller that will handle authentication requests.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.web.bind.annotation.*;
@RestController
@RequestMapping("/api/auth")
public class AuthController {
@Autowired
private JwtService jwtService;
@Autowired
private PasswordEncoder passwordEncoder;
@PostMapping("/login")
public String login(@RequestBody User loginRequest) {
// Example: static user for tests
User user = new User("admin", passwordEncoder.encode("password"));
if (user.getUsername().equals(loginRequest.getUsername()) &&
passwordEncoder.matches(loginRequest.getPassword(), user.getPassword())) {
return jwtService.generateToken(user.getUsername());
}
throw new RuntimeException("Invalid credentials");
}
}
This endpoint accepts username and password, compares them with the user data (in this case encoded in memory), and if everything is valid returns a JWT.
Step 6. JWT validation
Now we'll add a filter that processes every request and checks the provided token.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {
private final String SECRET_KEY = "your-256-bit-secret";
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token = request.getHeader("Authorization");
if (token != null && token.startsWith("Bearer ")) {
token = token.substring(7); // Remove "Bearer "
try {
Claims claims = Jwts.parser()
.setSigningKey(SECRET_KEY)
.parseClaimsJws(token)
.getBody();
String username = claims.getSubject();
// Set authentication
UsernamePasswordAuthenticationToken auth =
new UsernamePasswordAuthenticationToken(username, null, null);
SecurityContextHolder.getContext().setAuthentication(auth);
} catch (Exception e) {
System.out.println("Invalid token.");
}
}
filterChain.doFilter(request, response);
}
}
Don't forget to register this filter in the configuration.
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests()
.antMatchers("/api/auth/**").permitAll()
.anyRequest().authenticated()
.and()
.addFilterBefore(new JwtAuthenticationFilter(), UsernamePasswordAuthenticationFilter.class);
return http.build();
}
Step 7. Testing
Now we can test the application:
- Send a POST request to
/api/auth/loginwith JSON:{ "username": "admin", "password": "password" }You'll get a JWT in the response.
- Use that JWT in the
Authorizationheader for any protected route:Authorization: Bearer <your-token>
That's it! We've secured our REST API with JWT. In real life you'll need many improvements: storing users in a database, handling refresh tokens, and much more. But now you know the basic principles.
GO TO FULL VERSION