CodeGym /Courses /Module 5. Spring /Lecture 104: Setting up authentication with JWT

Lecture 104: Setting up authentication with JWT

Module 5. Spring
Level 18 , Lesson 3
Available

In this lecture we'll cover:

  1. How to configure Spring Boot to work with JWT.
  2. Implementing JWT token generation after successful authentication.
  3. Setting up a security filter to verify tokens in requests.
  4. Managing access to protected resources using JWT and Spring Security.

Ready? Brew some coffee and let's go!


Configuring Spring Boot to work with JWT

First, create a basic Spring Boot project. Make sure your pom.xml or build.gradle includes the necessary dependencies:

Dependencies

If you're using Maven, add this to your pom.xml:


<dependencies>
  <!-- Spring Web -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-web</artifactId>
  </dependency>

  <!-- Spring Security -->
  <dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
  </dependency>

  <!-- JWT library -->
  <dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
  </dependency>
</dependencies>

Or, if you're using Gradle:


dependencies {
    implementation 'org.springframework.boot:spring-boot-starter-web'
    implementation 'org.springframework.boot:spring-boot-starter-security'
    implementation 'io.jsonwebtoken:jjwt:0.9.1'
}

These dependencies will provide the tools you need to implement JWT authentication.


JWT token generation

Let's generate JWT tokens. Create a class JwtTokenUtil that will handle creating and validating tokens.

Implementation of JwtTokenUtil


import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;

import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class JwtTokenUtil {

    private static final String SECRET_KEY = "secret"; // Secret key (don't use such simple keys in real life!)
    private static final long EXPIRATION_TIME = 1000 * 60 * 60 * 10; // 10 hours

    // Token generation
    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>(); // You can add custom data here
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(username) // Set the username as the token subject
                .setIssuedAt(new Date()) // Token issuance date
                .setExpiration(new Date(System.currentTimeMillis() + EXPIRATION_TIME)) // Expiration time
                .signWith(SignatureAlgorithm.HS512, SECRET_KEY) // Use HMAC-SHA512 for signing
                .compact();
    }

    // Token validation (check if token is expired and matches the user)
    public boolean validateToken(String token, String username) {
        final String tokenUsername = extractUsername(token);
        return (username.equals(tokenUsername) && !isTokenExpired(token));
    }

    // Extract username from token
    public String extractUsername(String token) {
        return extractAllClaims(token).getSubject();
    }

    // Check token expiration
    public boolean isTokenExpired(String token) {
        return extractAllClaims(token).getExpiration().before(new Date());
    }

    // Extract all claims (token payload)
    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(SECRET_KEY).parseClaimsJws(token).getBody();
    }
}

In this class we implemented the main methods to work with JWT. Now we can generate tokens, validate them, and extract data from the payload.


Protecting resources with JWT

Now let's set up security. We'll use Spring Security to filter requests and verify tokens.

Security filter setup

Add a filter that will intercept requests, extract the JWT from the request header, and validate it.


import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

@Component
public class JwtRequestFilter extends OncePerRequestFilter {

    private final JwtTokenUtil jwtTokenUtil;

    public JwtRequestFilter(JwtTokenUtil jwtTokenUtil) {
        this.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;

        // Extract the token from the "Authorization: Bearer ..." header
        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtTokenUtil.extractUsername(jwt);
        }

        // Validate the token and set the security context
        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            if (jwtTokenUtil.validateToken(jwt, username)) {
                // Create authentication based on the token (simplified example)
                UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(
                        username, null, new ArrayList<>()
                );
                auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
                SecurityContextHolder.getContext().setAuthentication(auth);
            }
        }

        // Continue the filter chain
        chain.doFilter(request, response);
    }
}

This filter will validate the token for each request and add user info to the security context.


Spring Security configuration

Create a security configuration class to register our JwtRequestFilter and configure access to protected resources.


import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    private final JwtRequestFilter jwtRequestFilter;

    public SecurityConfig(JwtRequestFilter jwtRequestFilter) {
        this.jwtRequestFilter = jwtRequestFilter;
    }

    @Bean
    public PasswordEncoder passwordEncoder() {
        return new BCryptPasswordEncoder();
    }

    @Bean
    public AuthenticationManager authenticationManager(HttpSecurity httpSecurity) throws Exception {
        return httpSecurity.getSharedObject(AuthenticationManagerBuilder.class)
                .build();
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeHttpRequests()
            .requestMatchers("/authenticate").permitAll() // Open access for authentication
            .anyRequest().authenticated() // All other requests require authentication
            .and()
            .addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

Authentication endpoint implementation

Create a controller that accepts a username and password, then returns a JWT on successful authentication.


import org.springframework.web.bind.annotation.*;

@RestController
public class AuthController {

    private final JwtTokenUtil jwtTokenUtil;

    public AuthController(JwtTokenUtil jwtTokenUtil) {
        this.jwtTokenUtil = jwtTokenUtil;
    }

    @PostMapping("/authenticate")
    public String authenticate(@RequestBody AuthRequest authRequest) {
        // Simple username check (for demo — use UserDetailsService in real apps)
        if ("user".equals(authRequest.getUsername()) && "password".equals(authRequest.getPassword())) {
            return jwtTokenUtil.generateToken(authRequest.getUsername());
        } else {
            throw new RuntimeException("Invalid credentials");
        }
    }
}

// DTO for authentication request
class AuthRequest {
    private String username;
    private String password;

    // Getters and Setters
}

Now, by sending a POST request to /authenticate with valid credentials, you'll get a JWT that can be used to access protected resources.


At this point you have a basic app with JWT authentication. We've built a structure that's easy to scale for more complex projects. In real life, use a database to store users and roles, and add more layers of security checks.

Comments
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION