Fading Coder

One Final Commit for the Last Sprint

Home > Tech > Content

Implementing JWT Authentication and Authorization in Spring Boot Applications

Tech 1

JWT (JSON Web Token) is an open standard for securely transmitting information between parties as a JSON object. It is commonly used for authentication and authorization in web applications. Tokens can be sent via URL parameters, POST requests, or HTTP headers. The payload contains all necessary user information, reducing the need for frequent database queries.

JWT is primarily used for authorization, where each request includes a token after user login, granting access to permitted routes, services, and resources. Single sign-on is a key feature of JWT. It also facilitates information exchange through signing with public/private keys.

Advantages include cross-language compatibility due to JSON format, ability to store non-sensitive business logic data in the payload, compact size for efficient transmission, and statelessness on the server side for easy scalability. Disadvantages involve security risks if excessive sensitive data is stored in tokens, and potential performance overhead when stored in cookies.

JWT Structure

A JWT consists of three parts separated by dots: header, payload, and signature.

Header

The header specifies the token type and encryption algorithm, typically HMAC SHA256.

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

Payload

The payload contains claims, which are statements about an entity. There are three types: registered claims, public claims, and private claims.

Registered claims include:

  • iss: Issuer
  • sub: Subject
  • aud: Audience
  • exp: Expiration time
  • iat: Issued at time
  • nbf: Not before time
  • jti: JWT ID

Public claims are defined by the parties using JWT, while private claims are custom claims for sharing information.

Example payload:

{
  "iss": "auth-server",
  "iat": 1708699474,
  "exp": 1708799474,
  "aud": "client-app",
  "sub": "user@example.com",
  "role": "admin",
  "department": "engineering"
}

Signature

The signature is created by encoding the header and payload with Base64URL, concatenating them with a dot, and signing with a secret key using the specified algorithm.

Basic JWT Implementation

Typically, JWTs are included in the Authorization header with the Bearer scheme. The flow involves the server issuing a token upon user authentication, which the client includes in subsequent requests for validation.

Dependencies

Add the following dependency to your Maven project:

<dependency>
    <groupId>com.auth0</groupId>
    <artifactId>java-jwt</artifactId>
    <version>4.4.0</version>
</dependency>

Token Generation

import com.auth0.jwt.JWT;
import com.auth0.jwt.algorithms.Algorithm;
import java.time.Instant;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

public class TokenGenerator {
    private static final String SECRET_KEY = "secure-secret-key";

    public static String generateToken() {
        Date expiryDate = new Date(System.currentTimeMillis() + 30000); // 30 seconds

        Map<String, Object> headerClaims = new HashMap<>();
        headerClaims.put("typ", "JWT");
        headerClaims.put("alg", "HS256");

        return JWT.create()
                .withHeader(headerClaims)
                .withIssuer("auth-service")
                .withIssuedAt(Date.from(Instant.now()))
                .withExpiresAt(expiryDate)
                .withSubject("user@domain.com")
                .withAudience("api-service")
                .withClaim("userRole", "editor")
                .withClaim("userId", 42)
                .sign(Algorithm.HMAC256(SECRET_KEY));
    }
}

Token Validation

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTVerifier;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.exceptions.JWTVerificationException;
import com.auth0.jwt.interfaces.DecodedJWT;

public class TokenValidator {
    private static final String SECRET_KEY = "secure-secret-key";

    public static void validateToken(String token) {
        try {
            JWTVerifier verifier = JWT.require(Algorithm.HMAC256(SECRET_KEY))
                    .build();
            DecodedJWT decoded = verifier.verify(token);
            System.out.println("User ID: " + decoded.getClaim("userId"));
            System.out.println("User Role: " + decoded.getClaim("userRole"));
        } catch (JWTVerificationException e) {
            System.err.println("Token validation failed: " + e.getMessage());
        }
    }
}

Common exceptions include TokenExpiredException, SignatureVerificationException, AlgorithmMismatchException, and IncorrectClaimException.

Spring Boot Integration

Implement JWT authentication in a Spring Boot application with an interceptor for validation.

Project Dependencies

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 
         https://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>3.1.5</version>
    </parent>
    <groupId>com.example</groupId>
    <artifactId>jwt-demo</artifactId>
    <version>1.0.0</version>

    <properties>
        <java.version>17</java.version>
    </properties>

    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>com.auth0</groupId>
            <artifactId>java-jwt</artifactId>
            <version>4.4.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <optional>true</optional>
        </dependency>
    </dependencies>
</project>

JWT Utility Class

package com.example.util;

import com.auth0.jwt.JWT;
import com.auth0.jwt.JWTCreator;
import com.auth0.jwt.algorithms.Algorithm;
import com.auth0.jwt.interfaces.DecodedJWT;
import java.util.Calendar;
import java.util.Map;

public class JwtHelper {
    private static final String SECRET = "application-secret";

    public static String createToken(Map<String, String> claims) {
        Calendar expiration = Calendar.getInstance();
        expiration.add(Calendar.HOUR, 24);

        JWTCreator.Builder tokenBuilder = JWT.create();
        claims.forEach(tokenBuilder::withClaim);

        return tokenBuilder.withExpiresAt(expiration.getTime())
                .sign(Algorithm.HMAC256(SECRET));
    }

    public static DecodedJWT verifyToken(String token) {
        return JWT.require(Algorithm.HMAC256(SECRET))
                .build()
                .verify(token);
    }
}

Controller Implementation

package com.example.controller;

import com.example.util.JwtHelper;
import org.springframework.http.ResponseEntity;
import org.springframework.web.bind.annotation.*;
import java.util.HashMap;
import java.util.Map;

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

    @PostMapping("/authenticate")
    public ResponseEntity<Map<String, Object>> authenticate(
            @RequestParam String username,
            @RequestParam String password) {
        Map<String, Object> response = new HashMap<>();

        // Validate credentials (simplified for demonstration)
        if ("admin".equals(username) && "password123".equals(password)) {
            Map<String, String> claims = new HashMap<>();
            claims.put("username", username);
            claims.put("role", "administrator");

            String token = JwtHelper.createToken(claims);
            response.put("success", true);
            response.put("token", token);
            response.put("message", "Authentication successful");
        } else {
            response.put("success", false);
            response.put("message", "Invalid credentials");
        }
        return ResponseEntity.ok(response);
    }

    @GetMapping("/protected")
    public ResponseEntity<Map<String, Object>> getProtectedData(
            @RequestHeader("Authorization") String authHeader) {
        Map<String, Object> response = new HashMap<>();

        try {
            String token = authHeader.replace("Bearer ", "");
            JwtHelper.verifyToken(token);
            response.put("success", true);
            response.put("data", "Sensitive information accessed successfully");
        } catch (Exception e) {
            response.put("success", false);
            response.put("error", "Token validation failed");
        }
        return ResponseEntity.ok(response);
    }
}

Interceptor Configuration

package com.example.interceptor;

import com.example.util.JwtHelper;
import com.fasterxml.jackson.databind.ObjectMapper;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.http.MediaType;
import org.springframework.web.servlet.HandlerInterceptor;
import java.io.PrintWriter;
import java.util.HashMap;
import java.util.Map;

public class JwtInterceptor implements HandlerInterceptor {
    @Override
    public boolean preHandle(HttpServletRequest request,
                             HttpServletResponse response,
                             Object handler) throws Exception {
        String authHeader = request.getHeader("Authorization");
        Map<String, Object> result = new HashMap<>();

        if (authHeader == null || !authHeader.startsWith("Bearer ")) {
            result.put("success", false);
            result.put("message", "Missing or invalid authorization header");
            sendResponse(response, result);
            return false;
        }

        try {
            String token = authHeader.substring(7);
            JwtHelper.verifyToken(token);
            return true;
        } catch (Exception e) {
            result.put("success", false);
            result.put("message", "Token validation failed: " + e.getMessage());
            sendResponse(response, result);
            return false;
        }
    }

    private void sendResponse(HttpServletResponse response,
                              Map<String, Object> data) throws Exception {
        response.setContentType(MediaType.APPLICATION_JSON_VALUE);
        PrintWriter writer = response.getWriter();
        writer.write(new ObjectMapper().writeValueAsString(data));
        writer.flush();
    }
}

Register the interceptor:

package com.example.config;

import com.example.interceptor.JwtInterceptor;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.servlet.config.annotation.InterceptorRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;

@Configuration
public class WebConfig implements WebMvcConfigurer {
    @Override
    public void addInterceptors(InterceptorRegistry registry) {
        registry.addInterceptor(new JwtInterceptor())
                .addPathPatterns("/api/protected")
                .excludePathPatterns("/api/authenticate");
    }
}

Related Articles

Understanding Strong and Weak References in Java

Strong References Strong reference are the most prevalent type of object referencing in Java. When an object has a strong reference pointing to it, the garbage collector will not reclaim its memory. F...

Comprehensive Guide to SSTI Explained with Payload Bypass Techniques

Introduction Server-Side Template Injection (SSTI) is a vulnerability in web applications where user input is improper handled within the template engine and executed on the server. This exploit can r...

Implement Image Upload Functionality for Django Integrated TinyMCE Editor

Django’s Admin panel is highly user-friendly, and pairing it with TinyMCE, an effective rich text editor, simplifies content management significantly. Combining the two is particular useful for bloggi...

Leave a Comment

Anonymous

◎Feel free to join the discussion and share your thoughts.