Step-by-Step Guide to Implement JWT Authentication in a Spring Boot Application

Step-by-Step Guide to Implement JWT Authentication in a Spring Boot Application

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:

  1. 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..."
   }
  1. Access Protected Resources:
    Use the generated JWT to access protected endpoints by adding it to the Authorization 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:

  1. Add the necessary dependencies for Spring Security and JWT.
  2. Create a JwtTokenUtil utility class for token generation and validation.
  3. Implement a custom UserDetailsService to load user data.
  4. Create a JwtRequestFilter to intercept and validate JWTs in incoming requests.
  5. Configure Spring Security to use JWT in your SecurityConfigurer.
  6. Create an AuthenticationController to handle user authentication and generate JWTs.
  7. 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.

Comments

No comments yet. Why don’t you start the discussion?

Leave a Reply

Your email address will not be published. Required fields are marked *