Step-by-Step JWT Implementation in Java (Spring Boot)
JWT (JSON Web Token) is a compact, URL-safe means of representing claims securely between two parties. In the Java ecosystem, JWTs are widely used for authentication and authorization in RESTful services.
In this article, you will learn a step-by-step to implement JWT (JSON Web Token) in Java, typically using Spring Boot, JJWT (Java JWT library), and Spring Security.
Prerequisites
- Java 8+
- Spring Boot (2.x or 3.x)
- Maven or Gradle
- Dependency: jjwt for JWT handling
1. Add Dependencies
<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>
2. Create a JWT Utility Class
Create a JWT Utility Class to create and validate JWT token.
package com.example.demo.security; import io.jsonwebtoken.*; import io.jsonwebtoken.security.Keys; import org.springframework.stereotype.Component; import java.security.Key; import java.util.Date; @Component public class JwtUtil { private final Key key = Keys.secretKeyFor(SignatureAlgorithm.HS256); private final long expirationTime = 1000 * 60 * 60; // 1 hour public String generateToken(String username, Long userId, String role) { return Jwts.builder() .setSubject(username) .claim("userId", userId) .claim("role", role) .setIssuer("demoApp") .setIssuedAt(new Date()) .setExpiration(new Date(System.currentTimeMillis() + expirationTime)) .signWith(key) .compact(); } public boolean validateToken(String token) { try { Jwts.parserBuilder().setSigningKey(key).build().parseClaimsJws(token); return true; } catch (JwtException e) { return false; } } public Claims extractAllClaims(String token) { return Jwts.parserBuilder() .setSigningKey(key) .build() .parseClaimsJws(token) .getBody(); } public String extractUsername(String token) { return extractAllClaims(token).getSubject(); } public Long extractUserId(String token) { return extractAllClaims(token).get("userId", Long.class); } public String extractRole(String token) { return extractAllClaims(token).get("role", String.class); } }
3. Create Authentication Endpoint
package com.example.demo.controller; import com.example.demo.model.AuthRequest; import com.example.demo.model.AuthResponse; import com.example.demo.security.JwtUtil; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.http.*; import org.springframework.web.bind.annotation.*; @RestController public class AuthController { @Autowired private JwtUtil jwtUtil; @PostMapping("/authenticate") public ResponseEntity<?> authenticate(@RequestBody AuthRequest request) { // Validate username/password (in production, use a proper user service) if ("admin".equals(request.getUsername()) && "password".equals(request.getPassword())) { String token = jwtUtil.generateToken("admin", 101L, "USER"); return ResponseEntity.ok(new AuthResponse(token)); } return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Invalid credentials"); } }
AuthRequest and AuthResponse Classes
public class AuthRequest { private String username; private String password; // Getters and Setters } public class AuthResponse { private String token; public AuthResponse(String token) { this.token = token; } public String getToken() { return token; } }
4. Return Bookmarked Articles API (JWT Protected)
@RestController @RequestMapping("/api") public class BookmarkController { @Autowired private JwtUtil jwtUtil; @GetMapping("/bookmarks") public ResponseEntity<?> getBookmarks(@RequestHeader("Authorization") String authHeader) { if (authHeader == null || !authHeader.startsWith("Bearer ")) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Missing or invalid token"); } String token = authHeader.substring(7); if (!jwtUtil.validateToken(token)) { return ResponseEntity.status(HttpStatus.UNAUTHORIZED).body("Token is invalid or expired"); } Long userId = jwtUtil.extractUserId(token); String username = jwtUtil.extractUsername(token); // Mocked list of articles based on user List<String> bookmarks = List.of( "How JWT Works – A Guide for " + username, "Spring Boot Security Tips", "Top 10 Java Interview Questions" ); return ResponseEntity.ok(Map.of( "user", username, "userId", userId, "bookmarks", bookmarks )); } }
In above api we are validating token this is not scalable because more endpoints = more repeated logic and higher chance of errors. To resolve this we need to do this on a centralize point. that can be achieved by creating JwtFilter and SecurityConfig files
Create JwtFilter to Intercept Requests
The JwtFilter acts as a centralized gatekeeper that runs before your controller logic. It extracts the JWT from the request, validates it, and sets the authenticated user in Spring Security’s SecurityContext. By the time your controller handles the request, the user is already authenticated and accessible via SecurityContextHolder.
Imagine a security guard at the gate (JwtFilter) That If all is good, the person is allowed into the building (controller) without needing to show ID again.
package com.example.demo.security; import jakarta.servlet.*; import jakarta.servlet.http.*; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.security.authentication.UsernamePasswordAuthenticationToken; import org.springframework.security.core.context.SecurityContextHolder; import org.springframework.stereotype.Component; import org.springframework.web.filter.OncePerRequestFilter; import java.io.IOException; import java.util.Collections; @Component public class JwtFilter extends OncePerRequestFilter { @Autowired private JwtUtil jwtUtil; @Override protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException { String authHeader = request.getHeader("Authorization"); if (authHeader != null && authHeader.startsWith("Bearer ")) { String token = authHeader.substring(7); if (jwtUtil.validateToken(token)) { String username = jwtUtil.extractUsername(token); UsernamePasswordAuthenticationToken authToken = new UsernamePasswordAuthenticationToken(username, null, Collections.emptyList()); SecurityContextHolder.getContext().setAuthentication(authToken); } } filterChain.doFilter(request, response); } }
Configure Spring Security (SecurityConfig.java)
SecurityConfig defines the security rules for your application. It specifies which routes are public (like /authenticate) and which ones require authentication (like /api/**). It also integrates your custom JwtFilter into Spring’s security pipeline.
package com.example.demo.security; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.annotation.*; import org.springframework.security.config.annotation.web.builders.HttpSecurity; import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity; import org.springframework.security.web.*; import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter; @Configuration @EnableWebSecurity public class SecurityConfig { @Autowired private JwtFilter jwtFilter; @Bean public SecurityFilterChain filterChain(HttpSecurity http) throws Exception { return http .csrf().disable() .authorizeHttpRequests() .requestMatchers("/authenticate").permitAll() .anyRequest().authenticated() .and() .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class) .build(); } }