Community for developers to learn, share their programming knowledge. Register!
User Authentication and Authorization

Securing REST APIs with JWT in Spring Boot


In today's digital landscape, securing applications against unauthorized access is paramount. This article will guide you through the process of securing REST APIs using JSON Web Tokens (JWT) in the context of user authentication and authorization with Spring Boot. If you're looking to enhance your skills, you can get training on this article and elevate your understanding of security in API development.

Understanding JSON Web Tokens (JWT)

JSON Web Tokens (JWT) are a compact and self-contained way to represent information between two parties securely. A JWT is essentially a string comprised of three parts: a header, a payload, and a signature, which are separated by dots (.).

Header: Typically consists of two parts: the type of the token (JWT) and the signing algorithm being used, such as HMAC SHA256 or RSA.

Example of a header:

{
  "alg": "HS256",
  "typ": "JWT"
}

Payload: Contains the claims or the data you want to transmit. Claims can be of three types: registered, public, and private. Registered claims are predefined and include fields like iss (issuer), exp (expiration time), and sub (subject).

Example of a payload:

{
  "sub": "1234567890",
  "name": "John Doe",
  "iat": 1516239022
}

Signature: To create the signature part, you take the encoded header, the encoded payload, a secret, and sign it using the algorithm specified in the header. This ensures that the token cannot be altered without invalidating the signature.

A full JWT looks like this:

eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6IkpvaG4gRG9lIiwiaWF0IjoxNTE2MjM5MDIyfQ.SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c

JWTs are often used in authentication scenarios because they are stateless, meaning the server does not need to store any session information. Instead, all the necessary information is contained within the token itself, allowing for easier scalability in distributed systems.

Implementing JWT Authentication in Spring Security

To secure your REST APIs with JWT in Spring Boot, you'll need to implement a few key components. This process includes setting up Spring Security, creating a JWT utility class, and defining a filter for JWT validation.

Step 1: Add Dependencies

Ensure you have the following dependencies in your pom.xml:

<dependency>
    <groupId>io.jsonwebtoken</groupId>
    <artifactId>jjwt</artifactId>
    <version>0.9.1</version>
</dependency>
<dependency>
    <groupId>org.springframework.boot</groupId>
    <artifactId>spring-boot-starter-security</artifactId>
</dependency>

Step 2: Create JWT Utility Class

The JWT utility class is responsible for generating and validating the tokens.

import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import io.jsonwebtoken.SignatureAlgorithm;
import org.springframework.stereotype.Component;

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

@Component
public class JwtUtil {
    private String secretKey = "secret"; // Ideally loaded from environment variables

    public String generateToken(String username) {
        Map<String, Object> claims = new HashMap<>();
        return createToken(claims, username);
    }

    private String createToken(Map<String, Object> claims, String subject) {
        return Jwts.builder()
                .setClaims(claims)
                .setSubject(subject)
                .setIssuedAt(new Date(System.currentTimeMillis()))
                .setExpiration(new Date(System.currentTimeMillis() + 1000 * 60 * 60 * 10)) // 10 hours
                .signWith(SignatureAlgorithm.HS256, secretKey)
                .compact();
    }

    public Boolean validateToken(String token, String username) {
        final String extractedUsername = extractUsername(token);
        return (extractedUsername.equals(username) && !isTokenExpired(token));
    }

    private String extractUsername(String token) {
        return extractAllClaims(token).getSubject();
    }

    private Claims extractAllClaims(String token) {
        return Jwts.parser().setSigningKey(secretKey).parseClaimsJws(token).getBody();
    }

    private Boolean isTokenExpired(String token) {
        return extractAllClaims(token).getExpiration().before(new Date());
    }
}

Step 3: Configure Spring Security

Next, you need to configure Spring Security to use JWT for authentication. This involves creating a filter to intercept requests and validate the JWT.

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.web.authentication.WebAuthenticationFilter;

import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;

public class JwtRequestFilter extends WebAuthenticationFilter {

    @Autowired
    private JwtUtil jwtUtil;

    @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;

        if (authorizationHeader != null && authorizationHeader.startsWith("Bearer ")) {
            jwt = authorizationHeader.substring(7);
            username = jwtUtil.extractUsername(jwt);
        }

        if (username != null && SecurityContextHolder.getContext().getAuthentication() == null) {
            // Validate the token
            if (jwtUtil.validateToken(jwt, username)) {
                UsernamePasswordAuthenticationToken authenticationToken =
                        new UsernamePasswordAuthenticationToken(username, null, new ArrayList<>());
                SecurityContextHolder.getContext().setAuthentication(authenticationToken);
            }
        }
        chain.doFilter(request, response);
    }
}

Step 4: Register the Filter

Finally, register your filter in the Spring Security configuration.

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
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.config.annotation.web.configuration.WebSecurityConfigurerAdapter;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;

@Configuration
@EnableWebSecurity
public class SecurityConfig extends WebSecurityConfigurerAdapter {

    @Autowired
    private JwtRequestFilter jwtRequestFilter;

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http.csrf().disable()
            .authorizeRequests().antMatchers("/authenticate").permitAll()
            .anyRequest().authenticated()
            .and().sessionManagement().sessionCreationPolicy(SessionCreationPolicy.STATELESS);

        http.addFilterBefore(jwtRequestFilter, UsernamePasswordAuthenticationFilter.class);
    }
}

This configuration ensures that all requests to your REST API are authenticated using the JWT.

Configuring JWT Filters and Security Context

In the previous section, we established the basic JWT filter and registered it within the Spring Security context. It's crucial to understand how this filter operates in conjunction with the security context.

Security Context

The SecurityContext holds the authentication details of the user. When a valid JWT is presented, the JwtRequestFilter extracts the username and checks if it is valid. If it is, it creates a UsernamePasswordAuthenticationToken and sets it in the SecurityContextHolder. This allows you to retrieve the authenticated user details from anywhere in your application.

Fine-tuning Security

You might want to implement additional security measures, such as:

  • Role-based Access Control: Extend the JWT payload to include user roles and enforce authorization based on these roles.
  • Refresh Tokens: Implement a refresh token mechanism to allow users to obtain new JWTs without re-authenticating.
  • Token Revocation: Consider strategies for revoking tokens, such as maintaining a blacklist of revoked tokens.

Summary

Securing REST APIs with JWT in Spring Boot is an effective way to manage user authentication and authorization. By leveraging the stateless nature of JWTs, you can create scalable applications without compromising security.

In this article, we covered the essentials of JWT, how to implement JWT authentication using Spring Security, and how to configure JWT filters to validate requests. This comprehensive guide should equip you with the knowledge to secure your REST APIs effectively.

As you continue to develop your skills, consider looking into more advanced topics, such as role-based access control and token revocation strategies. With the right implementation, you can ensure that your applications remain secure and robust in an ever-evolving threat landscape.

Last Update: 28 Dec, 2024

Topics:
Spring Boot