Enterprise Java

How to Secure REST APIs with Spring Security and JWT (2025 Edition)

In 2025, stateless authentication remains the go-to approach for securing REST APIs. Spring Security combined with JWT (JSON Web Tokens) continues to be a powerful, scalable solution — but the practices around token handling, refresh flows, and endpoint protection have evolved.

This guide walks you through securing your Spring Boot REST APIs using Spring Security and JWT, with updated approaches for 2025.

⚙️ Why JWT for REST API Security?

JWT enables stateless authentication, eliminating the need for session storage. It allows clients to carry the user’s identity and permissions in an encrypted token.

Benefits:

  • Stateless = scalable across microservices
  • Easy integration with frontend/mobile clients
  • Self-contained (no DB lookups needed for each request)

🧱 Project Setup (Spring Boot 3.2+)

Make sure you’re using:

  • Java 21+
  • Spring Boot 3.2+
  • Spring Security 6+
  • jjwt or nimbus-jose-jwt for token handling

Add dependencies (Maven):

<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</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>

🔐 Creating the JWT Utility Class

@Component
public class JwtService {

    private final String secretKey = "your-256-bit-secret-key";
    private final Duration expiration = Duration.ofMinutes(15);

    public String generateToken(String username) {
        return Jwts.builder()
                .setSubject(username)
                .setIssuedAt(new Date())
                .setExpiration(Date.from(Instant.now().plus(expiration)))
                .signWith(Keys.hmacShaKeyFor(secretKey.getBytes()), SignatureAlgorithm.HS256)
                .compact();
    }

    public String extractUsername(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secretKey.getBytes())
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getSubject();
    }

    public boolean isValid(String token) {
        try {
            return !extractExpiration(token).before(new Date());
        } catch (JwtException e) {
            return false;
        }
    }

    private Date extractExpiration(String token) {
        return Jwts.parserBuilder()
                .setSigningKey(secretKey.getBytes())
                .build()
                .parseClaimsJws(token)
                .getBody()
                .getExpiration();
    }
}

🛡️ Configuring Spring Security with JWT Authentication Filter

@Configuration
@EnableWebSecurity
public class SecurityConfig {

    @Autowired
    private JwtAuthenticationFilter jwtFilter;

    @Bean
    public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
        return http
                .csrf(AbstractHttpConfigurer::disable)
                .sessionManagement(session -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
                .authorizeHttpRequests(auth -> auth
                        .requestMatchers("/api/auth/**").permitAll()
                        .anyRequest().authenticated())
                .addFilterBefore(jwtFilter, UsernamePasswordAuthenticationFilter.class)
                .build();
    }
}

JWT Filter:

@Component
public class JwtAuthenticationFilter extends OncePerRequestFilter {

    @Autowired
    private JwtService jwtService;

    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {

        final String authHeader = request.getHeader("Authorization");
        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            filterChain.doFilter(request, response);
            return;
        }

        final String token = authHeader.substring(7);
        if (jwtService.isValid(token)) {
            String username = jwtService.extractUsername(token);
            UsernamePasswordAuthenticationToken auth =
                    new UsernamePasswordAuthenticationToken(username, null, List.of());
            SecurityContextHolder.getContext().setAuthentication(auth);
        }

        filterChain.doFilter(request, response);
    }
}

🔁 Refresh Token Flow (2025 Best Practices)

To keep your JWTs short-lived (15 minutes or less), use a refresh token stored securely (e.g., HTTP-only cookie or secure local storage).

Sample Refresh Endpoint

@PostMapping("/api/auth/refresh")
public ResponseEntity<TokenResponse> refresh(@RequestBody RefreshRequest request) {
    String refreshToken = request.getRefreshToken();
    // Validate refresh token from DB or cache
    if (!refreshTokenService.isValid(refreshToken)) {
        return ResponseEntity.status(HttpStatus.UNAUTHORIZED).build();
    }

    String newAccessToken = jwtService.generateToken(request.getUsername());
    return ResponseEntity.ok(new TokenResponse(newAccessToken));
}

🧠 Tips for Production Security in 2025

  1. Use short-lived access tokens (≤ 15 mins)
  2. Use long-lived refresh tokens (7–30 days), stored securely
  3. Enable CORS and CSRF protection appropriately for SPAs
  4. Add IP/device metadata checks for refresh flows
  5. Implement token revocation logic (DB, cache, or blacklist)
  6. Use HTTPS exclusively

🧪 Testing with Postman or Curl

Login Request:

curl -X POST http://localhost:8080/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{"username":"john", "password":"password"}'

Authenticated Request:

curl http://localhost:8080/api/user/profile \
  -H "Authorization: Bearer <access_token>"

📚 Further Reading & Tools

✅ Conclusion

Securing REST APIs with JWT and Spring Security in 2025 means prioritizing stateless design, short-lived access tokens, and safe refresh token flows. With the latest Spring Security features, this setup is both powerful and clean — ready to scale with your modern web applications.

Eleftheria Drosopoulou

Eleftheria is an Experienced Business Analyst with a robust background in the computer software industry. Proficient in Computer Software Training, Digital Marketing, HTML Scripting, and Microsoft Office, they bring a wealth of technical skills to the table. Additionally, she has a love for writing articles on various tech subjects, showcasing a talent for translating complex concepts into accessible content.
Subscribe
Notify of
guest

This site uses Akismet to reduce spam. Learn how your comment data is processed.

0 Comments
Oldest
Newest Most Voted
Back to top button