Skip to main content

Command Palette

Search for a command to run...

Spring Cloud Gateway: Role-Based Authorization, Rate Limiting & Logging

(Java 25 | Centralized Config | Docker Ready)

Updated
β€’5 min read
P

πŸ‘¨β€πŸ’» I'm Praveen Gupta, a Senior Backend Engineer with over 10+ years of experience in designing scalable backend systems using Java, Spring Boot, Microservices, and AWS Cloud. I specialize in building cloud-native applications, integrating DevOps practices, and mentoring teams in writing clean, production-grade code. I created this blog to share real-world backend architecture tips, Spring Boot patterns, and practical coding solutions that help developers grow faster.

In the previous part, we implemented JWT token validation at Spring Cloud Gateway.
Now we’ll level it up to production standards by adding:

  • Role-based authorization

  • API rate limiting

  • Centralized request logging

  • Clear separation of public vs protected APIs


πŸ” Role-Based Authorization at Gateway

Instead of just validating the token, the Gateway will now check user roles.

Token Payload Example

{
  "sub": "praveen",
  "roles": ["USER", "ADMIN"],
  "exp": 1712345678
}

🧠 Authorization Flow

https://static.spring.io/blog/bwilcock/20190801/demo.png

https://docs.secureauth.com/ciam/en/image/uuid-15ab8611-c014-7de0-6511-7bb0cb605f64.svg

Steps

  1. Gateway extracts JWT

  2. Validates signature & expiry

  3. Extracts roles

  4. Matches roles against the API requirement

  5. Allows or blocks the request


πŸ›‘οΈ Role-Based Gateway Filter

@Component
public class RoleAuthFilter implements GatewayFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        String token = JwtUtil.extractToken(exchange);

        List<String> roles = JwtUtil.extractRoles(token);

        String path = exchange.getRequest().getURI().getPath();

        if (path.startsWith("/admin") && !roles.contains("ADMIN")) {
            exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }
}

πŸ›£οΈ Route Configuration with Roles

gateway.properties

spring.cloud.gateway.routes[0].id=product-service
spring.cloud.gateway.routes[0].uri=http://product-service:8082
spring.cloud.gateway.routes[0].predicates[0]=Path=/products/**
spring.cloud.gateway.routes[0].filters[0]=JwtAuthFilter
spring.cloud.gateway.routes[0].filters[1]=RoleAuthFilter

🚦 Rate Limiting at Gateway

Rate limiting protects services from abuse and accidental overload.

Why at Gateway?

βœ” One place
βœ” No code changes in services
βœ” Scales horizontally


πŸ“‰ Rate Limit Architecture

https://i0.wp.com/piotrminkowski.com/wp-content/uploads/2021/05/spring-cloud-gateway-secure-rate-limiter.png?resize=696%2C352&ssl=1

https://media.geeksforgeeks.org/wp-content/uploads/20230301163751/ratelimiter.png


βš™οΈ Simple Rate Limiting Filter (In-Memory)

@Component
public class RateLimitFilter implements GatewayFilter {

    private final Map<String, AtomicInteger> requestCounts = new ConcurrentHashMap<>();

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        String ip = exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();

        requestCounts.putIfAbsent(ip, new AtomicInteger(0));

        if (requestCounts.get(ip).incrementAndGet() > 100) {
            exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
            return exchange.getResponse().setComplete();
        }

        return chain.filter(exchange);
    }
}

πŸ’‘ In real production, replace this with Redis-based rate limiting.


πŸ“‹ Centralized Request Logging

Logging at Gateway gives full visibility across all services.


🧾 Logging Filter

@Component
public class LoggingFilter implements GlobalFilter {

    @Override
    public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {

        long startTime = System.currentTimeMillis();

        return chain.filter(exchange).then(
            Mono.fromRunnable(() -> {
                long duration = System.currentTimeMillis() - startTime;
                System.out.println(
                    exchange.getRequest().getMethod() + " " +
                    exchange.getRequest().getURI() +
                    " took " + duration + " ms"
                );
            })
        );
    }
}

🌍 Public vs Protected APIs

Public APIs (No Token Required)

  • /users/login

  • /users/register

  • /health

Protected APIs

  • /products/**

  • /admin/**


πŸ›£οΈ Public Route Configuration

spring.cloud.gateway.routes[2].id=public-user
spring.cloud.gateway.routes[2].uri=http://user-service:8081
spring.cloud.gateway.routes[2].predicates[0]=Path=/users/login

❌ No JWT filter
βœ… Open access


πŸ” End-to-End API Flow

https://user-images.githubusercontent.com/6509926/55875254-2c62e480-5b84-11e9-83bb-031eaf095476.png

https://microservices.io/i/microservices-auth/part-2/Security-Architecture.png

  1. Client logs in

  2. Receives JWT with roles

  3. Calls protected API

  4. Gateway:

    • Validates token

    • Checks roles

    • Applies rate limit

    • Logs request

  5. Forwards to service


βœ… Production Benefits

βœ” Centralized security
βœ” Zero duplication in services
βœ” Easy scaling
βœ” Cleaner microservices
βœ” Docker & Kubernetes friendly


πŸ“¦ Dockerfile (Common for All Services)

Use the same Dockerfile structure for Gateway, User, and Product services.

Dockerfile

# Java 25 runtime
FROM eclipse-temurin:25-jdk

# App directory
WORKDIR /app

# Copy jar
COPY target/*.jar app.jar

# Expose port (actual value comes from env)
EXPOSE 8080

# Run app
ENTRYPOINT ["java","-jar","app.jar"]

βœ” Java 25 compatible
βœ” Lightweight runtime
βœ” Environment-driven ports


βš™οΈ Centralized Config via Docker Volume

Your existing shared-config folder will be mounted inside containers.

Shared Config Structure

shared-config/
β”œβ”€β”€ application.properties
β”œβ”€β”€ gateway.properties
β”œβ”€β”€ user-service.properties
└── product-service.properties

🌐 Spring Config Import (All Services)

spring.config.import=optional:file:/config/application.properties

/config is mapped via Docker volume


🧱 Docker Compose Configuration

https://dz2cdn1.dzone.com/storage/temp/1858094-730f2922-ee20-11e5-8df0-e7b51c668847-2.png

docker-compose.yml

version: '3.8'

networks:
  microservices-net:

services:

  gateway-service:
    container_name: gateway-service
    build: ./gateway-service
    ports:
      - "8080:8080"
    environment:
      PORT: 8080
      APP_NAME: gateway-service
    volumes:
      - ./shared-config:/config
    networks:
      - microservices-net
    depends_on:
      - user-service
      - product-service

  user-service:
    container_name: user-service
    build: ./user-service
    ports:
      - "8081:8081"
    environment:
      PORT: 8081
      APP_NAME: user-service
    volumes:
      - ./shared-config:/config
    networks:
      - microservices-net

  product-service:
    container_name: product-service
    build: ./product-service
    ports:
      - "8082:8082"
    environment:
      PORT: 8082
      APP_NAME: product-service
    volumes:
      - ./shared-config:/config
    networks:
      - microservices-net

πŸ”— Service-to-Service Communication

Inside Docker network:

ServiceURL Used
Gateway β†’ Userhttp://user-service:8081
Gateway β†’ Producthttp://product-service:8082

βœ” Docker DNS resolves service names automatically
βœ” No Eureka required (optional later)


πŸ§ͺ Running the Setup

Step 1: Build JARs

mvn clean package -DskipTests

Step 2: Start All Services

docker-compose up --build

πŸ” Verify Containers

docker ps

Expected running containers:

  • gateway-service

  • user-service

  • product-service


πŸ” End-to-End Test

1️⃣ Login

POST http://localhost:8080/users/login

2️⃣ Access Protected API

GET http://localhost:8080/products
Authorization: Bearer <JWT>

βœ” Gateway validates token
βœ” Routes to Product Service
βœ” Docker handles networking


βœ… Why This Docker Setup Works Well

βœ” Clean separation of concerns
βœ” Centralized configuration
βœ” Easy local & CI testing
βœ” Kubernetes-ready structure
βœ” Zero environment-specific codeπŸ“Œ Final Thoughts

With this setup, Spring Cloud Gateway becomes the security brain of your system, while User & Product services stay lightweight and focused on business logic.


🧱 Root Project Structure

spring-cloud-gateway-microservices/
β”‚
β”œβ”€β”€ docker-compose.yml
β”œβ”€β”€ shared-config/
β”‚   β”œβ”€β”€ application.properties
β”‚   β”œβ”€β”€ gateway.properties
β”‚   β”œβ”€β”€ user-service.properties
β”‚   └── product-service.properties
β”‚
β”œβ”€β”€ gateway-service/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”œβ”€β”€ pom.xml
β”‚   └── src/
β”‚       └── main/
β”‚           β”œβ”€β”€ java/
β”‚           β”‚   └── com/example/gateway/
β”‚           β”‚       β”œβ”€β”€ GatewayApplication.java
β”‚           β”‚       β”œβ”€β”€ filter/
β”‚           β”‚       β”‚   β”œβ”€β”€ JwtAuthFilter.java
β”‚           β”‚       β”‚   β”œβ”€β”€ RoleAuthFilter.java
β”‚           β”‚       β”‚   β”œβ”€β”€ RateLimitFilter.java
β”‚           β”‚       β”‚   └── LoggingFilter.java
β”‚           β”‚       └── util/
β”‚           β”‚           └── JwtUtil.java
β”‚           β”‚
β”‚           └── resources/
β”‚               └── application.properties
β”‚
β”œβ”€β”€ user-service/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”œβ”€β”€ pom.xml
β”‚   └── src/
β”‚       └── main/
β”‚           β”œβ”€β”€ java/
β”‚           β”‚   └── com/example/user/
β”‚           β”‚       β”œβ”€β”€ UserServiceApplication.java
β”‚           β”‚       β”œβ”€β”€ controller/
β”‚           β”‚       β”‚   └── AuthController.java
β”‚           β”‚       β”œβ”€β”€ dto/
β”‚           β”‚       β”‚   └── LoginRequest.java
β”‚           β”‚       └── util/
β”‚           β”‚           └── JwtUtil.java
β”‚           β”‚
β”‚           └── resources/
β”‚               └── application.properties
β”‚
β”œβ”€β”€ product-service/
β”‚   β”œβ”€β”€ Dockerfile
β”‚   β”œβ”€β”€ pom.xml
β”‚   └── src/
β”‚       └── main/
β”‚           β”œβ”€β”€ java/
β”‚           β”‚   └── com/example/product/
β”‚           β”‚       β”œβ”€β”€ ProductServiceApplication.java
β”‚           β”‚       └── controller/
β”‚           β”‚           └── ProductController.java
β”‚           β”‚
β”‚           └── resources/
β”‚               └── application.properties
β”‚
└── README.md

πŸš€ What’s Next (Final Part)

If you want, next we can cover:

  • Redis-based rate limiting

  • Spring Security + Gateway

  • OpenAPI + Swagger via Gateway

  • Distributed tracing (Zipkin)

  • Kubernetes ingress migration

  • Complete GitHub repo structure


πŸ’¬ Feedback?

If you found this useful, leave a comment or connect with me on LinkedIn. πŸš€