Learn to implement JWT Authentication with Spring Security 6. Learn about token creation, validation, filters, securing APIs using modern best practices.
๐ What is JWT Authentication?
JWT (JSON Web Token) is a stateless, compact, and secure mechanism for transmitting user authentication data between parties.
A JWT typically consists of three parts:
1 2 3 4 |
header.payload.signature |
When a user logs in:
- A JWT is issued and sent to the client.
- For every secured request, the client includes this token in the
Authorization
header. - The server validates the token and grants access accordingly.

๐งฉ Project Structure
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
com.kscodes.springboot.security.jwt โโโ config/ โ โโโ SecurityConfig.java โโโ controller/ โ โโโ AuthController.java โโโ filter/ โ โโโ JwtAuthenticationFilter.java โโโ model/ โ โโโ AuthRequest.java โโโ service/ โ โโโ JwtService.java โโโ SpringBootJwtApplication.java |
๐ฆ Maven Dependencies
In pom.xml
:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
<dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-security</artifactId> </dependency> <dependency> <groupId>org.springframework.boot</groupId> <artifactId>spring-boot-starter-web</artifactId> </dependency> <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> <scope>runtime</scope> </dependency> <dependency> <groupId>io.jsonwebtoken</groupId> <artifactId>jjwt-jackson</artifactId> <version>0.11.5</version> <scope>runtime</scope> </dependency> |
๐ Security Configuration
SecurityConfig.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
package com.kscodes.springboot.security.jwt.config; import com.kscodes.springboot.security.jwt.filter.JwtAuthenticationFilter; 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.User; 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.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration public class SecurityConfig { private final JwtAuthenticationFilter jwtAuthFilter; public SecurityConfig(JwtAuthenticationFilter jwtAuthFilter) { this.jwtAuthFilter = jwtAuthFilter; } @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { http .csrf().disable() .authorizeHttpRequests(auth -> auth .requestMatchers("/api/auth/**").permitAll() .anyRequest().authenticated() ) .addFilterBefore(jwtAuthFilter, UsernamePasswordAuthenticationFilter.class); return http.build(); } @Bean public UserDetailsService userDetailsService() { return new InMemoryUserDetailsManager( User.withUsername("user") .password(passwordEncoder().encode("password")) .roles("USER") .build() ); } @Bean public PasswordEncoder passwordEncoder() { return new BCryptPasswordEncoder(); } @Bean public AuthenticationManager authenticationManager(AuthenticationConfiguration config) throws Exception { return config.getAuthenticationManager(); } } |
๐งช Auth Request DTO
AuthRequest.java
1 2 3 4 5 6 7 8 9 10 11 |
package com.kscodes.springboot.security.jwt.model; public class AuthRequest { private String username; private String password; // Getters and setters } |
๐ JWT Service
JwtService.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
package com.kscodes.springboot.security.jwt.service; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import org.springframework.security.core.userdetails.UserDetails; import org.springframework.stereotype.Service; import java.security.Key; import java.util.Date; @Service public class JwtService { private final String SECRET = "mySuperSecretKey12345678901234567890"; // 256-bit key public String generateToken(UserDetails userDetails) { return Jwts.builder() .setSubject(userDetails.getUsername()) .setIssuedAt(new Date(System.currentTimeMillis())) .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60)) // 1 hour .signWith(getSignKey(), SignatureAlgorithm.HS256) .compact(); } public boolean isTokenValid(String token, UserDetails userDetails) { final String username = extractUsername(token); return (username.equals(userDetails.getUsername()) && !isTokenExpired(token)); } public String extractUsername(String token) { return extractClaims(token).getSubject(); } private boolean isTokenExpired(String token) { return extractClaims(token).getExpiration().before(new Date()); } private Claims extractClaims(String token) { return Jwts.parserBuilder() .setSigningKey(getSignKey()) .build() .parseClaimsJws(token) .getBody(); } private Key getSignKey() { return Keys.hmacShaKeyFor(SECRET.getBytes()); } } |
๐งฑ JWT Authentication Filter
JwtAuthenticationFilter.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 |
package com.kscodes.springboot.security.jwt.filter; import com.kscodes.springboot.security.jwt.service.JwtService; 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.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; @Component public class JwtAuthenticationFilter extends OncePerRequestFilter { private final JwtService jwtService; private final UserDetailsService userDetailsService; public JwtAuthenticationFilter(JwtService jwtService, UserDetailsService userDetailsService) { this.jwtService = jwtService; this.userDetailsService = userDetailsService; } @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { final String authHeader = request.getHeader("Authorization"); final String jwt; final String username; if (authHeader == null || !authHeader.startsWith("Bearer ")) { filterChain.doFilter(request, response); return; } jwt = authHeader.substring(7); username = jwtService.extractUsername(jwt); if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) { UserDetails userDetails = this.userDetailsService.loadUserByUsername(username); if (jwtService.isTokenValid(jwt, userDetails)) { UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken( userDetails, null, userDetails.getAuthorities()); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } } |
๐ Authentication Controller
AuthController.java
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 |
package com.kscodes.springboot.security.jwt.controller; import com.kscodes.springboot.security.jwt.model.AuthRequest; import com.kscodes.springboot.security.jwt.service.JwtService; 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 @RequestMapping("/api/auth") public class AuthController { private final JwtService jwtService; private final AuthenticationManager authenticationManager; private final UserDetailsService userDetailsService; public AuthController(JwtService jwtService, AuthenticationManager authenticationManager, UserDetailsService userDetailsService) { this.jwtService = jwtService; this.authenticationManager = authenticationManager; this.userDetailsService = userDetailsService; } @PostMapping("/login") public String authenticate(@RequestBody AuthRequest authRequest) { authenticationManager.authenticate( new UsernamePasswordAuthenticationToken(authRequest.getUsername(), authRequest.getPassword()) ); UserDetails userDetails = userDetailsService.loadUserByUsername(authRequest.getUsername()); return jwtService.generateToken(userDetails); } } |
๐ Secured API Endpoint Example
1 2 3 4 5 6 7 8 9 10 11 12 |
@RestController @RequestMapping("/api/user") public class UserController { @GetMapping("/profile") public String profile() { return "This is a secured profile endpoint."; } } |
๐งช Testing the Flow
1. Authenticate and get token
1 2 3 4 5 6 |
curl -X POST http://localhost:8080/api/auth/login \ -H "Content-Type: application/json" \ -d '{"username":"user", "password":"password"}' |
๐ฅ Response:"eyJhbGciOiJIUzI1NiJ9..."
Access secured endpoint
1 2 3 4 5 |
curl http://localhost:8080/api/user/profile \ -H "Authorization: Bearer eyJhbGciOiJIUzI1NiJ9..." |
โ
Response:This is a secured profile endpoint.
๐ Conclusion
JWT Authentication with Spring Security 6 in a Spring Boot 3 application offers a stateless and secure mechanism to authenticate and authorize API access. With updated components like SecurityFilterChain
and modern JWT libraries, you can implement a clean and efficient security layer with minimal overhead.