JWT with Spring Boot

Here’s a clear, complete explanation + working structure for implementing JWT Authentication in Spring Boot (Spring Security 6 / Boot 3).

JWT Authentication with Spring Boot Example

You’ll learn:

  • How JWT works
  • Complete flow (with code)
  • Key classes
  • Example endpoints

🧠 What Is JWT Authentication?

JWT (JSON Web Token) = a secure, compact token that verifies a user’s identity.
It’s used to achieve stateless authentication — no sessions stored on the server.


🧩 How It Works (Flow)

  1. User sends credentials → POST /auth/login
  2. Server authenticates user
  3. Server generates a JWT token and sends it back
  4. User includes that token in future requests: Authorization: Bearer <jwt-token>
  5. Server validates the token on every request (no DB lookup needed)
  6. If valid → user is authenticated

⚙️ Project Setup

Dependencies (Gradle)

dependencies {
    implementation("org.springframework.boot:spring-boot-starter-web")
    implementation("org.springframework.boot:spring-boot-starter-security")
    implementation("io.jsonwebtoken:jjwt-api:0.11.5")
    runtimeOnly("io.jsonwebtoken:jjwt-impl:0.11.5")
    runtimeOnly("io.jsonwebtoken:jjwt-jackson:0.11.5")
}

1️⃣ JwtUtil – Token Generator & Validator

package com.example.security.jwt;

import io.jsonwebtoken.*;
import io.jsonwebtoken.security.Keys;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Component;

import java.security.Key;
import java.util.Date;

@Component
public class JwtUtil {
    private final Key key;
    private final long expiration;

    public JwtUtil(@Value("${jwt.secret}") String secret,
                   @Value("${jwt.expiration-ms}") long expiration) {
        this.key = Keys.hmacShaKeyFor(secret.getBytes());
        this.expiration = expiration;
    }

    public String generateToken(String username) {
        Date now = new Date();
        Date expiry = new Date(now.getTime() + expiration);

        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(now)
                .setExpiration(expiry)
                .signWith(key, SignatureAlgorithm.HS256)
                .compact();
    }

    public boolean validateToken(String token) {
        try {
            Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token);
            return true;
        } catch (JwtException e) {
            return false;
        }
    }

    public String getUsername(String token) {
        return Jwts.parserBuilder().setSigningKey(key).build()
                .parseClaimsJws(token).getBody().getSubject();
    }
}

2️⃣ Security Configuration

package com.example.security.config;

import com.example.security.filter.JwtAuthFilter;
import com.example.security.jwt.JwtUtil;
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.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
public class SecurityConfig {

    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;

    public SecurityConfig(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

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

    @Bean
    public AuthenticationManager authenticationManager(AuthenticationConfiguration configuration) throws Exception {
        return configuration.getAuthenticationManager();
    }

    @Bean
    public JwtAuthFilter jwtAuthFilter() {
        return new JwtAuthFilter(jwtUtil, userDetailsService);
    }

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        http.csrf(csrf -> csrf.disable())
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/auth/**").permitAll()
                .anyRequest().authenticated())
            .addFilterBefore(jwtAuthFilter(), UsernamePasswordAuthenticationFilter.class);

        return http.build();
    }
}

3️⃣ JWT Authentication Filter

package com.example.security.filter;

import com.example.security.jwt.JwtUtil;
import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
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.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;

import java.io.IOException;

public class JwtAuthFilter extends OncePerRequestFilter {
    private final JwtUtil jwtUtil;
    private final UserDetailsService userDetailsService;

    public JwtAuthFilter(JwtUtil jwtUtil, UserDetailsService userDetailsService) {
        this.jwtUtil = jwtUtil;
        this.userDetailsService = userDetailsService;
    }

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response,
                                    FilterChain filterChain) throws ServletException, IOException {
        String header = request.getHeader("Authorization");
        String token = null;
        if (StringUtils.hasText(header) && header.startsWith("Bearer ")) {
            token = header.substring(7);
        }

        if (token != null && jwtUtil.validateToken(token)) {
            String username = jwtUtil.getUsername(token);
            UserDetails userDetails = userDetailsService.loadUserByUsername(username);
            UsernamePasswordAuthenticationToken auth =
                    new UsernamePasswordAuthenticationToken(userDetails, null, userDetails.getAuthorities());
            auth.setDetails(new WebAuthenticationDetailsSource().buildDetails(request));
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}

4️⃣ AuthController – Login Endpoint

package com.example.security.controller;

import com.example.security.jwt.JwtUtil;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.Authentication;
import org.springframework.web.bind.annotation.*;

@RestController
@RequestMapping("/auth")
public class AuthController {

    private final AuthenticationManager authManager;
    private final JwtUtil jwtUtil;

    public AuthController(AuthenticationManager authManager, JwtUtil jwtUtil) {
        this.authManager = authManager;
        this.jwtUtil = jwtUtil;
    }

    @PostMapping("/login")
    public String login(@RequestBody LoginRequest request) {
        Authentication authentication = authManager.authenticate(
                new UsernamePasswordAuthenticationToken(request.username, request.password)
        );

        if (authentication.isAuthenticated()) {
            return jwtUtil.generateToken(request.username);
        } else {
            throw new RuntimeException("Invalid credentials");
        }
    }

    public record LoginRequest(String username, String password) {}
}

5️⃣ Example Protected Endpoint

package com.example.security.controller;

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

@RestController
@RequestMapping("/api")
public class DemoController {

    @GetMapping("/hello")
    public String hello() {
        return "Hello from secured endpoint!";
    }
}

6️⃣ application.properties

jwt.secret=ThisIsMySuperSecretJwtKey123456789
jwt.expiration-ms=3600000

🔍 How to Test

  1. Start your Spring Boot app.
  2. Call /auth/login:
curl -X POST http://localhost:8080/auth/login \
-H "Content-Type: application/json" \
-d '{"username":"user","password":"password"}'

→ You’ll get a JWT token.

3. Call protected endpoint:

curl http://localhost:8080/api/hello \
-H "Authorization: Bearer <your-token>"

✅ Should return "Hello from secured endpoint!"


🚀 Summary

StepDescription
Build tokenJwtUtil.generateToken()
Authenticate/auth/login
Validate tokenJwtAuthFilter
Secure routesSecurityFilterChain
AccessAuthorization: Bearer <token>

Production considerations (quick checklist)

  • Test JWT expiration and edge cases.
  • Use a long, random secret (rotate periodically), store in environment variables or secret manager.
  • Use HTTPS always.
  • Consider refresh tokens if you want long-lived sessions.
  • Add token revocation / blacklist if you need immediate logout capability.
  • Add claims (roles, tenant id) into JWT as needed — keep size reasonable.
  • Implement exception handling in the filter for expired/invalid tokens and map to proper HTTP status codes.
  • Use @PreAuthorize / method security for fine-grained access control.

Read other awesome articles in Medium.com or in akcoding’s posts.

OR

Join us on YouTube Channel

OR Scan the QR Code to Directly open the Channel 👉

AK Coding YouTube Channel

Share with