Implementing JWT authentication in a Spring Boot application involves a few essential steps. Here’s a breakdown of how to do it:
Steps to Implement JWT Authentication in Spring Boot:
1. Add Dependencies:
First, add the required dependencies for Spring Security and JWT in your pom.xml
(if you’re using Maven):
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<dependency>
<groupId>io.jsonwebtoken</groupId>
<artifactId>jjwt</artifactId>
<version>0.9.1</version> <!-- or latest version -->
</dependency>
2. Create a Model for User Authentication:
Create a simple User
model class to represent your users:
public class JwtRequest {
private String username;
private String password;
// Getters and Setters
}
public class JwtResponse {
private String token;
public JwtResponse(String token) {
this.token = token;
}
// Getter
}
3. Implement the JWT Utility Class:
You’ll need a utility class to generate and validate JWT tokens. You can use the jjwt
library to generate tokens.
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;
import java.util.Date;
import java.util.function.Function;
@Component
public class JwtTokenUtil {
private String SECRET_KEY = "your_secret_key"; // Use a strong secret key!
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(String username) {
return Jwts.builder()
.setSubject(username)
.setIssuedAt(new Date(System.currentTimeMillis()))
.setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
.signWith(SignatureAlgorithm.HS256, SECRET_KEY)
.compact();
}
public Boolean validateToken(String token, String username) {
final String extractedUsername = extractUsername(token);
return (extractedUsername.equals(username) && !isTokenExpired(token));
}
}
4. Create a Custom UserDetailsService:
Implement UserDetailsService
to load user-specific data.
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 {
@Override
public UserDetails loadUserByUsername(String username) throws UsernameNotFoundException {
// Here you should fetch the user from the database or any other source
if ("user".equals(username)) {
return new org.springframework.security.core.userdetails.User("user", "$2a$10$exampleHashValue", new ArrayList<>());
} else {
throw new UsernameNotFoundException("User not found");
}
}
}
5. Create JWT Request Filter:
This filter intercepts incoming requests, extracts the JWT, and validates it.
import io.jsonwebtoken.ExpiredJwtException;
import org.springframework.beans.factory.annotation.Autowired;
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.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
@Component
public class JwtRequestFilter extends OncePerRequestFilter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@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);
try {
username = jwtTokenUtil.extractUsername(jwt);
} catch (ExpiredJwtException e) {
// Handle token expiration
}
}
if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
UserDetails userDetails = this.userDetailsService.loadUserByUsername(username);
if (jwtTokenUtil.validateToken(jwt, userDetails.getUsername())) {
UsernamePasswordAuthenticationToken usernamePasswordAuthenticationToken =
new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
usernamePasswordAuthenticationToken.setDetails(
new WebAuthenticationDetailsSource().buildDetails(request));
SecurityContextHolder.getContext().setAuthentication(usernamePasswordAuthenticationToken);
}
}
chain.doFilter(request, response);
}
}
6. Configure Spring Security:
Now, configure Spring Security to use JWT for authentication.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.annotation.Bean;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.builders.AuthenticationManagerBuilder;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
@EnableWebSecurity
public class SecurityConfigurer extends WebSecurityConfigurerAdapter {
@Autowired
private CustomUserDetailsService userDetailsService;
@Autowired
private JwtRequestFilter jwtRequestFilter;
@Override
protected void configure(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService).passwordEncoder(passwordEncoder());
}
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder();
}
@Override
@Bean
public AuthenticationManager authenticationManagerBean() throws Exception {
return super.authenticationManagerBean();
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate", "/register").permitAll() // Allow public access to authentication and registration endpoints
.anyRequest().authenticated() // All other endpoints require authentication
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // Use stateless session (JWT)
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); // Add the JWT filter before Spring Security's UsernamePasswordAuthenticationFilter
}
Security Configuration (continued):
@Override
protected void configure(HttpSecurity http) throws Exception {
http.csrf().disable()
.authorizeRequests().antMatchers("/authenticate", "/register").permitAll() // Allow public access to authentication and registration endpoints
.anyRequest().authenticated() // All other endpoints require authentication
.and().sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS); // Use stateless session (JWT)
http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class); // Add the JWT filter before Spring Security's UsernamePasswordAuthenticationFilter
}
7. Create Authentication Controller:
This controller handles user login and token generation.
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.web.bind.annotation.*;
@RestController
public class AuthenticationController {
@Autowired
private AuthenticationManager authenticationManager;
@Autowired
private JwtTokenUtil jwtTokenUtil;
@Autowired
private CustomUserDetailsService userDetailsService;
@PostMapping("/authenticate")
public JwtResponse createAuthenticationToken(@RequestBody JwtRequest authenticationRequest) throws Exception {
// Authenticate the user
authenticationManager.authenticate(
new UsernamePasswordAuthenticationToken(authenticationRequest.getUsername(), authenticationRequest.getPassword())
);
// Load user details after authentication
final UserDetails userDetails = userDetailsService.loadUserByUsername(authenticationRequest.getUsername());
// Generate the JWT token
final String jwt = jwtTokenUtil.generateToken(userDetails.getUsername());
return new JwtResponse(jwt); // Return the JWT in the response
}
}
8. Test the API:
- POST /authenticate:
Send a POST request with the username and password in the request body to generate a JWT. Example Request:
POST /authenticate
Content-Type: application/json
{
"username": "user",
"password": "password123"
}
Example Response (with JWT):
{
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9..."
}
- Access Protected Resources:
Use the generated JWT to access protected endpoints by adding it to theAuthorization
header:
Authorization: Bearer <your-jwt-token>
9. Enable Global CORS Configuration (Optional):
If your application is accessed by different domains (e.g., frontend and backend are hosted separately), you may need to enable CORS (Cross-Origin Resource Sharing).
import org.springframework.context.annotation.Bean;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/**")
.allowedOrigins("http://your-frontend-domain.com")
.allowedMethods("*")
.allowedHeaders("*");
}
};
}
Summary of Steps:
- Add the necessary dependencies for Spring Security and JWT.
- Create a
JwtTokenUtil
utility class for token generation and validation. - Implement a custom
UserDetailsService
to load user data. - Create a
JwtRequestFilter
to intercept and validate JWTs in incoming requests. - Configure Spring Security to use JWT in your
SecurityConfigurer
. - Create an
AuthenticationController
to handle user authentication and generate JWTs. - Secure API endpoints by restricting access using JWT authentication.
Now you have a complete setup for JWT authentication in a Spring Boot application! Let me know if you need further assistance.