Introduction to Secure API Gateway
An API Gateway is a critical component in microservices architecture that acts as a single entry point for all client requests. It handles request routing, composition, and protocol translation while providing essential security features like authentication, authorization, rate limiting, and threat protection.
System Architecture Overview
Secure API Gateway Architecture ├── Security Layer │ ├── TLS Termination │ ├── Authentication (JWT, OAuth2, API Keys) │ ├── Authorization (RBAC, ABAC) │ ├── Rate Limiting │ └── IP Whitelisting/Blacklisting ├── Request Processing │ ├── Request/Response Transformation │ ├── Protocol Translation │ ├── Request Routing │ ├── Load Balancing │ └── Circuit Breaking ├── Threat Protection │ ├── SQL Injection Prevention │ ├── XSS Protection │ ├── CSRF Protection │ ├── Request Size Limiting │ └── DDoS Protection └── Observability ├── Request Logging ├── Metrics Collection ├── Distributed Tracing └── Health Checks
Core Implementation
1. Maven 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
http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.security.gateway</groupId>
<artifactId>secure-api-gateway</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>3.2.0</version>
</parent>
<properties>
<java.version>17</java.version>
<spring-cloud.version>2023.0.0</spring-cloud.version>
<resilience4j.version>2.1.0</resilience4j.version>
<bucket4j.version>8.7.0</bucket4j.version>
</properties>
<dependencies>
<!-- Spring Cloud Gateway -->
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-starter-gateway</artifactId>
</dependency>
<!-- Spring Security -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
<!-- OAuth2 Resource Server -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-oauth2-resource-server</artifactId>
</dependency>
<!-- Resilience4j for Circuit Breaking -->
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-spring-boot2</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<dependency>
<groupId>io.github.resilience4j</groupId>
<artifactId>resilience4j-reactor</artifactId>
<version>${resilience4j.version}</version>
</dependency>
<!-- Bucket4j for Rate Limiting -->
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-core</artifactId>
<version>${bucket4j.version}</version>
</dependency>
<dependency>
<groupId>com.bucket4j</groupId>
<artifactId>bucket4j-redis</artifactId>
<version>${bucket4j.version}</version>
</dependency>
<!-- Redis for Distributed Rate Limiting -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-redis-reactive</artifactId>
</dependency>
<!-- JWT Support -->
<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>
<!-- Micrometer for Metrics -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-registry-prometheus</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<!-- Distributed Tracing -->
<dependency>
<groupId>io.micrometer</groupId>
<artifactId>micrometer-tracing-bridge-brave</artifactId>
</dependency>
<dependency>
<groupId>io.zipkin.reporter2</groupId>
<artifactId>zipkin-reporter-brave</artifactId>
</dependency>
<!-- Validation -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-validation</artifactId>
</dependency>
<!-- Lombok -->
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<optional>true</optional>
</dependency>
<!-- Testing -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.projectreactor</groupId>
<artifactId>reactor-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>
<dependencyManagement>
<dependencies>
<dependency>
<groupId>org.springframework.cloud</groupId>
<artifactId>spring-cloud-dependencies</artifactId>
<version>${spring-cloud.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
</project>
2. Gateway Security Configuration
package com.security.gateway.config;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
import org.springframework.security.config.web.server.ServerHttpSecurity;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.authentication.ServerAuthenticationEntryPoint;
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.reactive.CorsConfigurationSource;
import org.springframework.web.cors.reactive.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
@Configuration
@EnableWebFluxSecurity
public class GatewaySecurityConfig {
@Bean
public SecurityWebFilterChain springSecurityFilterChain(ServerHttpSecurity http) {
return http
.csrf(ServerHttpSecurity.CsrfSpec::disable)
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
.authorizeExchange(exchanges -> exchanges
.pathMatchers("/public/**", "/actuator/health", "/actuator/info").permitAll()
.pathMatchers("/api/**").authenticated()
.anyExchange().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(jwt -> jwt
.jwtAuthenticationConverter(new JwtAuthenticationConverter())
)
.authenticationEntryPoint(authenticationEntryPoint())
.accessDeniedHandler(accessDeniedHandler())
)
.build();
}
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
configuration.setAllowedOrigins(Arrays.asList("https://trusted-domain.com"));
configuration.setAllowedMethods(Arrays.asList("GET", "POST", "PUT", "DELETE", "OPTIONS"));
configuration.setAllowedHeaders(Arrays.asList("Authorization", "Content-Type", "X-Request-ID"));
configuration.setAllowCredentials(true);
configuration.setMaxAge(3600L);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/**", configuration);
return source;
}
@Bean
public ServerAuthenticationEntryPoint authenticationEntryPoint() {
return (exchange, ex) -> {
exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
byte[] bytes = "{\"error\":\"Unauthorized\",\"message\":\"Invalid or missing token\"}".getBytes();
return exchange.getResponse().writeWith(reactor.core.publisher.Mono.just(
exchange.getResponse().bufferFactory().wrap(bytes)
));
};
}
@Bean
public ServerAccessDeniedHandler accessDeniedHandler() {
return (exchange, ex) -> {
exchange.getResponse().setStatusCode(org.springframework.http.HttpStatus.FORBIDDEN);
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
byte[] bytes = "{\"error\":\"Forbidden\",\"message\":\"Insufficient permissions\"}".getBytes();
return exchange.getResponse().writeWith(reactor.core.publisher.Mono.just(
exchange.getResponse().bufferFactory().wrap(bytes)
));
};
}
}
3. JWT Authentication Converter
package com.security.gateway.security;
import org.springframework.core.convert.converter.Converter;
import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import reactor.core.publisher.Mono;
import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import java.util.stream.Stream;
public class JwtAuthenticationConverter implements Converter<Jwt, Mono<AbstractAuthenticationToken>> {
private static final String REALM_ACCESS = "realm_access";
private static final String ROLES = "roles";
private static final String RESOURCE_ACCESS = "resource_access";
private static final String CLIENT_ID = "gateway-client";
@Override
public Mono<AbstractAuthenticationToken> convert(Jwt jwt) {
Collection<GrantedAuthority> authorities = extractAuthorities(jwt);
return Mono.just(new JwtAuthenticationToken(jwt, authorities));
}
private Collection<GrantedAuthority> extractAuthorities(Jwt jwt) {
return Stream.concat(
extractRealmRoles(jwt).stream(),
extractResourceRoles(jwt).stream()
).collect(Collectors.toSet());
}
private Collection<GrantedAuthority> extractRealmRoles(Jwt jwt) {
Collection<GrantedAuthority> authorities = new java.util.ArrayList<>();
if (jwt.containsClaim(REALM_ACCESS)) {
Map<String, Object> realmAccess = jwt.getClaim(REALM_ACCESS);
if (realmAccess.containsKey(ROLES)) {
List<String> roles = (List<String>) realmAccess.get(ROLES);
authorities.addAll(roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
.collect(Collectors.toList()));
}
}
return authorities;
}
private Collection<GrantedAuthority> extractResourceRoles(Jwt jwt) {
Collection<GrantedAuthority> authorities = new java.util.ArrayList<>();
if (jwt.containsClaim(RESOURCE_ACCESS)) {
Map<String, Object> resourceAccess = jwt.getClaim(RESOURCE_ACCESS);
if (resourceAccess.containsKey(CLIENT_ID)) {
Map<String, Object> clientAccess = (Map<String, Object>) resourceAccess.get(CLIENT_ID);
if (clientAccess.containsKey(ROLES)) {
List<String> roles = (List<String>) clientAccess.get(ROLES);
authorities.addAll(roles.stream()
.map(role -> new SimpleGrantedAuthority("ROLE_" + role.toUpperCase()))
.collect(Collectors.toList()));
}
}
}
return authorities;
}
}
4. Rate Limiting Filter
package com.security.gateway.filter;
import com.bucket4j.Bandwidth;
import com.bucket4j.Bucket;
import com.bucket4j.Bucket4j;
import com.bucket4j.Refill;
import com.bucket4j.grid.GridBucket;
import io.github.bucket4j.redis.lettuce.cas.LettuceBasedProxyManager;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class RateLimitingFilter implements GatewayFilter, Ordered {
private final Map<String, Bucket> localBuckets = new ConcurrentHashMap<>();
@Autowired(required = false)
private ReactiveRedisTemplate<String, String> redisTemplate;
private LettuceBasedProxyManager proxyManager;
private static final String RATE_LIMIT_KEY_PREFIX = "rate-limit:";
private static final int DEFAULT_CAPACITY = 10;
private static final int DEFAULT_REFILL_TOKENS = 10;
private static final Duration DEFAULT_REFILL_DURATION = Duration.ofMinutes(1);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String clientId = extractClientId(exchange);
if (useRedis()) {
return handleDistributedRateLimit(clientId, exchange, chain);
} else {
return handleLocalRateLimit(clientId, exchange, chain);
}
}
private Mono<Void> handleLocalRateLimit(String clientId, ServerWebExchange exchange,
GatewayFilterChain chain) {
Bucket bucket = localBuckets.computeIfAbsent(clientId, this::createLocalBucket);
if (bucket.tryConsume(1)) {
exchange.getResponse().getHeaders().add("X-RateLimit-Remaining",
String.valueOf(bucket.getAvailableTokens()));
return chain.filter(exchange);
} else {
return handleRateLimitExceeded(exchange);
}
}
private Mono<Void> handleDistributedRateLimit(String clientId, ServerWebExchange exchange,
GatewayFilterChain chain) {
String key = RATE_LIMIT_KEY_PREFIX + clientId;
return redisTemplate.opsForValue().get(key)
.flatMap(value -> {
// Parse bucket state from Redis
Bucket bucket = createDistributedBucket(key);
if (bucket.tryConsume(1)) {
exchange.getResponse().getHeaders().add("X-RateLimit-Remaining",
String.valueOf(bucket.getAvailableTokens()));
return chain.filter(exchange);
} else {
return handleRateLimitExceeded(exchange);
}
})
.switchIfEmpty(
// First request for this client
createInitialBucket(key).then(chain.filter(exchange))
);
}
private Mono<Void> handleRateLimitExceeded(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.TOO_MANY_REQUESTS);
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
String errorResponse = "{\"error\":\"Too Many Requests\",\"message\":\"Rate limit exceeded\"}";
byte[] bytes = errorResponse.getBytes();
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(bytes))
);
}
private Bucket createLocalBucket(String clientId) {
Bandwidth limit = Bandwidth.classic(DEFAULT_CAPACITY,
Refill.greedy(DEFAULT_REFILL_TOKENS, DEFAULT_REFILL_DURATION));
return Bucket4j.builder()
.addLimit(limit)
.build();
}
private Bucket createDistributedBucket(String key) {
Bandwidth limit = Bandwidth.classic(DEFAULT_CAPACITY,
Refill.greedy(DEFAULT_REFILL_TOKENS, DEFAULT_REFILL_DURATION));
return GridBucket.builder()
.addLimit(limit)
.build(proxyManager, key);
}
private Mono<Void> createInitialBucket(String key) {
String bucketState = String.format("%d:%d", DEFAULT_CAPACITY, DEFAULT_CAPACITY);
return redisTemplate.opsForValue().set(key, bucketState, DEFAULT_REFILL_DURATION).then();
}
private String extractClientId(ServerWebExchange exchange) {
// Try to extract from JWT first
String authHeader = exchange.getRequest().getHeaders().getFirst("Authorization");
if (authHeader != null && authHeader.startsWith("Bearer ")) {
// Parse JWT and extract client_id
return "jwt-client";
}
// Fallback to API key or IP
String apiKey = exchange.getRequest().getHeaders().getFirst("X-API-Key");
if (apiKey != null) {
return "apikey:" + apiKey;
}
// Use IP address as last resort
String ip = exchange.getRequest().getRemoteAddress() != null ?
exchange.getRequest().getRemoteAddress().getAddress().getHostAddress() : "unknown";
return "ip:" + ip;
}
private boolean useRedis() {
return redisTemplate != null;
}
@Override
public int getOrder() {
return -1; // High priority
}
}
5. Authentication Filter (JWT Validation)
package com.security.gateway.filter;
import io.jsonwebtoken.Claims;
import io.jsonwebtoken.Jwts;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.security.KeyFactory;
import java.security.PublicKey;
import java.security.spec.X509EncodedKeySpec;
import java.util.Base64;
import java.util.List;
@Slf4j
@Component
public class JwtAuthenticationFilter implements GatewayFilter, Ordered {
@Value("${security.jwt.public-key}")
private String publicKeyString;
@Value("${security.jwt.issuer}")
private String expectedIssuer;
private PublicKey publicKey;
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String authHeader = exchange.getRequest().getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
if (authHeader == null || !authHeader.startsWith("Bearer ")) {
return handleUnauthorized(exchange, "Missing or invalid Authorization header");
}
String token = authHeader.substring(7);
try {
Claims claims = validateToken(token);
// Add claims to request headers for downstream services
ServerHttpRequest mutatedRequest = exchange.getRequest().mutate()
.header("X-User-ID", claims.getSubject())
.header("X-User-Roles", claims.get("roles", String.class))
.build();
ServerWebExchange mutatedExchange = exchange.mutate()
.request(mutatedRequest)
.build();
return chain.filter(mutatedExchange);
} catch (Exception e) {
log.error("JWT validation failed: {}", e.getMessage());
return handleUnauthorized(exchange, "Invalid token: " + e.getMessage());
}
}
private Claims validateToken(String token) throws Exception {
if (publicKey == null) {
initPublicKey();
}
return Jwts.parserBuilder()
.setSigningKey(publicKey)
.requireIssuer(expectedIssuer)
.build()
.parseClaimsJws(token)
.getBody();
}
private void initPublicKey() throws Exception {
byte[] keyBytes = Base64.getDecoder().decode(publicKeyString);
X509EncodedKeySpec spec = new X509EncodedKeySpec(keyBytes);
KeyFactory keyFactory = KeyFactory.getInstance("RSA");
publicKey = keyFactory.generatePublic(spec);
}
private Mono<Void> handleUnauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
String errorResponse = String.format("{\"error\":\"Unauthorized\",\"message\":\"%s\"}", message);
byte[] bytes = errorResponse.getBytes();
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(bytes))
);
}
@Override
public int getOrder() {
return -2; // Run before rate limiting
}
}
6. API Key Authentication Filter
package com.security.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.data.redis.core.ReactiveRedisTemplate;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class ApiKeyAuthenticationFilter implements GatewayFilter, Ordered {
@Autowired
private ReactiveRedisTemplate<String, String> redisTemplate;
private final Map<String, ApiKeyInfo> apiKeyCache = new ConcurrentHashMap<>();
private static final String API_KEY_HEADER = "X-API-Key";
private static final String API_KEY_PREFIX = "apikey:";
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String apiKey = exchange.getRequest().getHeaders().getFirst(API_KEY_HEADER);
if (apiKey == null || apiKey.isEmpty()) {
return handleUnauthorized(exchange, "Missing API Key");
}
return validateApiKey(apiKey)
.flatMap(apiKeyInfo -> {
if (apiKeyInfo == null || !apiKeyInfo.isValid()) {
return handleUnauthorized(exchange, "Invalid API Key");
}
// Add client info to request headers
ServerWebExchange mutatedExchange = exchange.mutate()
.request(exchange.getRequest().mutate()
.header("X-Client-ID", apiKeyInfo.getClientId())
.header("X-Client-Tier", apiKeyInfo.getTier())
.build())
.build();
return chain.filter(mutatedExchange);
});
}
private Mono<ApiKeyInfo> validateApiKey(String apiKey) {
// Check cache first
ApiKeyInfo cached = apiKeyCache.get(apiKey);
if (cached != null && !cached.isExpired()) {
return Mono.just(cached);
}
// Check Redis
String key = API_KEY_PREFIX + hashApiKey(apiKey);
return redisTemplate.opsForHash().entries(key)
.collectMap(
entry -> (String) entry.getKey(),
entry -> (String) entry.getValue()
)
.map(map -> {
if (map.isEmpty()) {
return null;
}
ApiKeyInfo info = ApiKeyInfo.builder()
.apiKey(apiKey)
.clientId(map.get("clientId"))
.tier(map.get("tier"))
.enabled(Boolean.parseBoolean(map.get("enabled")))
.expiresAt(Long.parseLong(map.get("expiresAt")))
.build();
// Cache for 5 minutes
apiKeyCache.put(apiKey, info);
return info;
});
}
private String hashApiKey(String apiKey) {
try {
java.security.MessageDigest digest =
java.security.MessageDigest.getInstance("SHA-256");
byte[] hash = digest.digest(apiKey.getBytes());
return Base64.getEncoder().encodeToString(hash);
} catch (Exception e) {
log.error("Failed to hash API key", e);
return apiKey;
}
}
private Mono<Void> handleUnauthorized(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.UNAUTHORIZED);
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
String errorResponse = String.format("{\"error\":\"Unauthorized\",\"message\":\"%s\"}", message);
byte[] bytes = errorResponse.getBytes();
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(bytes))
);
}
@Override
public int getOrder() {
return -3; // Run before JWT filter
}
@lombok.Data
@lombok.Builder
public static class ApiKeyInfo {
private String apiKey;
private String clientId;
private String tier;
private boolean enabled;
private long expiresAt;
public boolean isValid() {
return enabled && expiresAt > System.currentTimeMillis();
}
public boolean isExpired() {
return expiresAt <= System.currentTimeMillis();
}
}
}
7. Request/Response Transformation Filter
package com.security.gateway.filter;
import com.fasterxml.jackson.databind.JsonNode;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.node.ObjectNode;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.core.io.buffer.DataBufferUtils;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpStatus;
import org.springframework.http.MediaType;
import org.springframework.http.server.reactive.ServerHttpRequest;
import org.springframework.http.server.reactive.ServerHttpResponse;
import org.springframework.http.server.reactive.ServerHttpResponseDecorator;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import java.nio.charset.StandardCharsets;
import java.util.List;
import java.util.regex.Pattern;
@Slf4j
@Component
public class RequestResponseTransformFilter implements GatewayFilter, Ordered {
private final ObjectMapper objectMapper = new ObjectMapper();
// SQL Injection patterns
private static final List<Pattern> SQL_PATTERNS = List.of(
Pattern.compile("(?i).*\\b(UNION|SELECT|INSERT|UPDATE|DELETE|DROP|ALTER|CREATE)\\b.*"),
Pattern.compile("(?i).*\\b(OR|AND)\\s+\\w+\\s*=\\s*\\w+\\b.*"),
Pattern.compile(".*['\"].*\\bOR\\b.*['\"].*")
);
// XSS patterns
private static final List<Pattern> XSS_PATTERNS = List.of(
Pattern.compile(".*<script\\b.*?>.*</script>.*", Pattern.CASE_INSENSITIVE),
Pattern.compile(".*javascript:.*", Pattern.CASE_INSENSITIVE),
Pattern.compile(".*onerror\\s*=.*", Pattern.CASE_INSENSITIVE),
Pattern.compile(".*onload\\s*=.*", Pattern.CASE_INSENSITIVE)
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
ServerHttpRequest request = exchange.getRequest();
// Validate request
if (!validateRequest(request)) {
return handleInvalidRequest(exchange, "Request contains prohibited content");
}
// Sanitize headers
ServerHttpRequest sanitizedRequest = sanitizeHeaders(request);
// Transform response
ServerHttpResponseDecorator decoratedResponse = new ServerHttpResponseDecorator(exchange.getResponse()) {
@Override
public Mono<Void> writeWith(Flux<? extends DataBuffer> body) {
if (getStatusCode() == HttpStatus.OK &&
getHeaders().getContentType() != null &&
getHeaders().getContentType().includes(MediaType.APPLICATION_JSON)) {
return super.writeWith(body.map(this::sanitizeResponseBody));
}
return super.writeWith(body);
}
private DataBuffer sanitizeResponseBody(DataBuffer buffer) {
try {
byte[] bytes = new byte[buffer.readableByteCount()];
buffer.read(bytes);
DataBufferUtils.release(buffer);
String content = new String(bytes, StandardCharsets.UTF_8);
JsonNode json = objectMapper.readTree(content);
// Remove sensitive data
if (json.isObject()) {
((ObjectNode) json).remove(List.of(
"password", "secret", "token", "credit_card", "ssn"
));
}
String sanitized = objectMapper.writeValueAsString(json);
byte[] sanitizedBytes = sanitized.getBytes(StandardCharsets.UTF_8);
return exchange.getResponse().bufferFactory().wrap(sanitizedBytes);
} catch (Exception e) {
log.error("Failed to sanitize response", e);
return buffer;
}
}
};
ServerWebExchange mutatedExchange = exchange.mutate()
.request(sanitizedRequest)
.response(decoratedResponse)
.build();
return chain.filter(mutatedExchange);
}
private boolean validateRequest(ServerHttpRequest request) {
// Check query parameters
for (String param : request.getQueryParams().keySet()) {
List<String> values = request.getQueryParams().get(param);
if (values != null) {
for (String value : values) {
if (containsMaliciousContent(value)) {
log.warn("Malicious content detected in query param: {}", param);
return false;
}
}
}
}
// Check headers
for (String headerName : request.getHeaders().keySet()) {
List<String> headerValues = request.getHeaders().get(headerName);
if (headerValues != null) {
for (String value : headerValues) {
if (containsMaliciousContent(value)) {
log.warn("Malicious content detected in header: {}", headerName);
return false;
}
}
}
}
return true;
}
private boolean containsMaliciousContent(String value) {
if (value == null) return false;
// Check SQL injection patterns
for (Pattern pattern : SQL_PATTERNS) {
if (pattern.matcher(value).matches()) {
return true;
}
}
// Check XSS patterns
for (Pattern pattern : XSS_PATTERNS) {
if (pattern.matcher(value).matches()) {
return true;
}
}
return false;
}
private ServerHttpRequest sanitizeHeaders(ServerHttpRequest request) {
HttpHeaders headers = new HttpHeaders();
headers.putAll(request.getHeaders());
// Remove sensitive headers
headers.remove("X-Forwarded-For");
headers.remove("X-Real-IP");
headers.remove("X-Original-URI");
// Add security headers
headers.set("X-Content-Type-Options", "nosniff");
headers.set("X-Frame-Options", "DENY");
headers.set("X-XSS-Protection", "1; mode=block");
return request.mutate()
.headers(h -> h.putAll(headers))
.build();
}
private Mono<Void> handleInvalidRequest(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.BAD_REQUEST);
exchange.getResponse().getHeaders().setContentType(MediaType.APPLICATION_JSON);
String errorResponse = String.format("{\"error\":\"Bad Request\",\"message\":\"%s\"}", message);
byte[] bytes = errorResponse.getBytes();
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(bytes))
);
}
@Override
public int getOrder() {
return -4; // Run before authentication
}
}
8. Circuit Breaker Filter
package com.security.gateway.filter;
import io.github.resilience4j.circuitbreaker.CallNotPermittedException;
import io.github.resilience4j.circuitbreaker.CircuitBreaker;
import io.github.resilience4j.circuitbreaker.CircuitBreakerConfig;
import io.github.resilience4j.circuitbreaker.CircuitBreakerRegistry;
import io.github.resilience4j.reactor.circuitbreaker.operator.CircuitBreakerOperator;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
@Slf4j
@Component
public class CircuitBreakerFilter implements GatewayFilter, Ordered {
private final CircuitBreakerRegistry circuitBreakerRegistry;
private final Map<String, CircuitBreaker> circuitBreakers = new ConcurrentHashMap<>();
public CircuitBreakerFilter() {
CircuitBreakerConfig config = CircuitBreakerConfig.custom()
.slidingWindowSize(10)
.failureRateThreshold(50)
.waitDurationInOpenState(Duration.ofSeconds(30))
.permittedNumberOfCallsInHalfOpenState(3)
.automaticTransitionFromOpenToHalfOpenEnabled(true)
.recordExceptions(Throwable.class)
.build();
this.circuitBreakerRegistry = CircuitBreakerRegistry.of(config);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String serviceName = extractServiceName(exchange);
CircuitBreaker circuitBreaker = getOrCreateCircuitBreaker(serviceName);
return chain.filter(exchange)
.transformDeferred(CircuitBreakerOperator.of(circuitBreaker))
.onErrorResume(CallNotPermittedException.class, e -> {
log.warn("Circuit breaker open for service: {}", serviceName);
return handleCircuitBreakerOpen(exchange);
})
.onErrorResume(Exception.class, e -> {
log.error("Service error for {}: {}", serviceName, e.getMessage());
circuitBreaker.onError(0, e);
return Mono.error(e);
});
}
private CircuitBreaker getOrCreateCircuitBreaker(String serviceName) {
return circuitBreakers.computeIfAbsent(serviceName,
name -> circuitBreakerRegistry.circuitBreaker(name));
}
private String extractServiceName(ServerWebExchange exchange) {
String path = exchange.getRequest().getPath().value();
if (path.startsWith("/api/users")) {
return "user-service";
} else if (path.startsWith("/api/orders")) {
return "order-service";
} else if (path.startsWith("/api/products")) {
return "product-service";
} else {
return "default";
}
}
private Mono<Void> handleCircuitBreakerOpen(ServerWebExchange exchange) {
exchange.getResponse().setStatusCode(HttpStatus.SERVICE_UNAVAILABLE);
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
String errorResponse = "{\"error\":\"Service Unavailable\",\"message\":\"Circuit breaker is open\"}";
byte[] bytes = errorResponse.getBytes();
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(bytes))
);
}
@Override
public int getOrder() {
return 1; // Run after authentication
}
}
9. Gateway Route Configuration
package com.security.gateway.config;
import com.security.gateway.filter.*;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cloud.gateway.route.RouteLocator;
import org.springframework.cloud.gateway.route.builder.RouteLocatorBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
@Configuration
public class GatewayRouteConfig {
@Autowired
private JwtAuthenticationFilter jwtAuthFilter;
@Autowired
private ApiKeyAuthenticationFilter apiKeyFilter;
@Autowired
private RateLimitingFilter rateLimitingFilter;
@Autowired
private RequestResponseTransformFilter transformFilter;
@Autowired
private CircuitBreakerFilter circuitBreakerFilter;
@Bean
public RouteLocator customRouteLocator(RouteLocatorBuilder builder) {
return builder.routes()
// Public routes (no authentication)
.route("public", r -> r
.path("/public/**")
.filters(f -> f
.filter(transformFilter)
.filter(rateLimitingFilter)
.circuitBreaker(config -> config
.setName("publicServiceCB")
.setFallbackUri("forward:/fallback/public")))
.uri("lb://PUBLIC-SERVICE"))
// User service routes (JWT authentication)
.route("users", r -> r
.path("/api/users/**")
.filters(f -> f
.filter(transformFilter)
.filter(jwtAuthFilter)
.filter(rateLimitingFilter)
.filter(circuitBreakerFilter)
.rewritePath("/api/users/(?<segment>.*)", "/${segment}"))
.uri("lb://USER-SERVICE"))
// Order service routes (JWT authentication)
.route("orders", r -> r
.path("/api/orders/**")
.filters(f -> f
.filter(transformFilter)
.filter(jwtAuthFilter)
.filter(rateLimitingFilter)
.filter(circuitBreakerFilter)
.rewritePath("/api/orders/(?<segment>.*)", "/${segment}"))
.uri("lb://ORDER-SERVICE"))
// Product service routes (API Key authentication)
.route("products", r -> r
.path("/api/products/**")
.filters(f -> f
.filter(transformFilter)
.filter(apiKeyFilter)
.filter(rateLimitingFilter)
.filter(circuitBreakerFilter)
.rewritePath("/api/products/(?<segment>.*)", "/${segment}"))
.uri("lb://PRODUCT-SERVICE"))
// Partner API routes (API Key + additional validation)
.route("partners", r -> r
.path("/api/partners/**")
.filters(f -> f
.filter(transformFilter)
.filter(apiKeyFilter)
.filter(rateLimitingFilter)
.filter(circuitBreakerFilter)
.setRequestHeader("X-Partner-API", "true")
.rewritePath("/api/partners/(?<segment>.*)", "/${segment}"))
.uri("lb://PARTNER-SERVICE"))
// Webhook routes (no authentication, IP whitelist)
.route("webhooks", r -> r
.path("/webhooks/**")
.filters(f -> f
.filter(transformFilter)
.filter(rateLimitingFilter)
.circuitBreaker(config -> config
.setName("webhookCB")
.setFallbackUri("forward:/fallback/webhook")))
.uri("lb://WEBHOOK-SERVICE"))
// Fallback route
.route("fallback", r -> r
.path("/fallback/**")
.uri("lb://FALLBACK-SERVICE"))
.build();
}
}
10. IP Whitelist Filter
package com.security.gateway.filter;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.List;
import java.util.regex.Pattern;
import java.util.stream.Collectors;
@Slf4j
@Component
public class IpWhitelistFilter implements GatewayFilter, Ordered {
@Value("${gateway.ip-whitelist:}")
private String whitelistString;
private List<Pattern> whitelistPatterns;
private static final List<String> INTERNAL_IPS = Arrays.asList(
"127.0.0.1", "::1", "10.0.0.0/8", "172.16.0.0/12", "192.168.0.0/16"
);
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
String clientIp = getClientIp(exchange);
if (!isIpWhitelisted(clientIp)) {
log.warn("Blocked request from non-whitelisted IP: {}", clientIp);
return handleForbidden(exchange, "IP not whitelisted: " + clientIp);
}
return chain.filter(exchange);
}
private String getClientIp(ServerWebExchange exchange) {
String xForwardedFor = exchange.getRequest().getHeaders().getFirst("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
String xRealIp = exchange.getRequest().getHeaders().getFirst("X-Real-IP");
if (xRealIp != null && !xRealIp.isEmpty()) {
return xRealIp;
}
if (exchange.getRequest().getRemoteAddress() != null) {
return exchange.getRequest().getRemoteAddress().getAddress().getHostAddress();
}
return "unknown";
}
private boolean isIpWhitelisted(String ip) {
if (whitelistPatterns == null) {
initWhitelistPatterns();
}
// Always allow internal IPs if not explicitly blocked
if (isInternalIp(ip)) {
return true;
}
// Check against whitelist patterns
for (Pattern pattern : whitelistPatterns) {
if (pattern.matcher(ip).matches()) {
return true;
}
}
return false;
}
private boolean isInternalIp(String ip) {
try {
InetAddress address = InetAddress.getByName(ip);
if (address.isLoopbackAddress() || address.isSiteLocalAddress()) {
return true;
}
// Check CIDR ranges
for (String internalIp : INTERNAL_IPS) {
if (internalIp.contains("/")) {
if (isIpInCidr(ip, internalIp)) {
return true;
}
}
}
} catch (UnknownHostException e) {
log.error("Failed to parse IP: {}", ip, e);
}
return false;
}
private boolean isIpInCidr(String ip, String cidr) {
String[] parts = cidr.split("/");
String network = parts[0];
int prefixLength = Integer.parseInt(parts[1]);
try {
long ipLong = ipToLong(InetAddress.getByName(ip));
long networkLong = ipToLong(InetAddress.getByName(network));
long mask = -1L << (32 - prefixLength);
return (ipLong & mask) == (networkLong & mask);
} catch (UnknownHostException e) {
return false;
}
}
private long ipToLong(InetAddress ip) {
byte[] octets = ip.getAddress();
long result = 0;
for (byte octet : octets) {
result <<= 8;
result |= octet & 0xFF;
}
return result;
}
private void initWhitelistPatterns() {
if (whitelistString == null || whitelistString.isEmpty()) {
this.whitelistPatterns = List.of();
} else {
this.whitelistPatterns = Arrays.stream(whitelistString.split(","))
.map(String::trim)
.map(pattern -> pattern.replace(".", "\\.").replace("*", ".*"))
.map(Pattern::compile)
.collect(Collectors.toList());
}
}
private Mono<Void> handleForbidden(ServerWebExchange exchange, String message) {
exchange.getResponse().setStatusCode(HttpStatus.FORBIDDEN);
exchange.getResponse().getHeaders().setContentType(org.springframework.http.MediaType.APPLICATION_JSON);
String errorResponse = String.format("{\"error\":\"Forbidden\",\"message\":\"%s\"}", message);
byte[] bytes = errorResponse.getBytes();
return exchange.getResponse().writeWith(
Mono.just(exchange.getResponse().bufferFactory().wrap(bytes))
);
}
@Override
public int getOrder() {
return -5; // Run before all other filters
}
}
11. Monitoring and Metrics
package com.security.gateway.monitoring;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import lombok.extern.slf4j.Slf4j;
import org.springframework.cloud.gateway.filter.GatewayFilter;
import org.springframework.cloud.gateway.filter.GatewayFilterChain;
import org.springframework.core.Ordered;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import reactor.core.publisher.Mono;
import java.time.Duration;
import java.util.concurrent.TimeUnit;
@Slf4j
@Component
public class MetricsFilter implements GatewayFilter, Ordered {
private final MeterRegistry meterRegistry;
private final Counter totalRequests;
private final Counter successfulRequests;
private final Counter failedRequests;
private final Counter blockedRequests;
public MetricsFilter(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.totalRequests = Counter.builder("gateway.requests.total")
.description("Total requests processed by gateway")
.register(meterRegistry);
this.successfulRequests = Counter.builder("gateway.requests.successful")
.description("Successful requests")
.register(meterRegistry);
this.failedRequests = Counter.builder("gateway.requests.failed")
.description("Failed requests")
.register(meterRegistry);
this.blockedRequests = Counter.builder("gateway.requests.blocked")
.description("Blocked requests")
.register(meterRegistry);
}
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
totalRequests.increment();
String path = exchange.getRequest().getPath().value();
String method = exchange.getRequest().getMethod().name();
Timer.Sample sample = Timer.start(meterRegistry);
return chain.filter(exchange)
.doOnSuccess(v -> {
sample.stop(Timer.builder("gateway.request.duration")
.tag("path", path)
.tag("method", method)
.tag("status", String.valueOf(exchange.getResponse().getStatusCode()))
.register(meterRegistry));
if (exchange.getResponse().getStatusCode() == HttpStatus.OK) {
successfulRequests.increment();
} else {
failedRequests.increment();
}
recordCustomMetrics(exchange);
})
.doOnError(error -> {
sample.stop(Timer.builder("gateway.request.duration")
.tag("path", path)
.tag("method", method)
.tag("error", error.getClass().getSimpleName())
.register(meterRegistry));
failedRequests.increment();
log.error("Request failed: {} {}", method, path, error);
});
}
private void recordCustomMetrics(ServerWebExchange exchange) {
// Record request size
String contentLength = exchange.getRequest().getHeaders().getFirst("Content-Length");
if (contentLength != null) {
meterRegistry.summary("gateway.request.size")
.record(Long.parseLong(contentLength));
}
// Record response size
String responseLength = exchange.getResponse().getHeaders().getFirst("Content-Length");
if (responseLength != null) {
meterRegistry.summary("gateway.response.size")
.record(Long.parseLong(responseLength));
}
// Count by status code
HttpStatus status = exchange.getResponse().getStatusCode();
if (status != null) {
Counter.builder("gateway.response.status")
.tag("status", String.valueOf(status.value()))
.register(meterRegistry)
.increment();
}
// Count by path
String path = exchange.getRequest().getPath().value();
Counter.builder("gateway.request.path")
.tag("path", path)
.register(meterRegistry)
.increment();
}
@Override
public int getOrder() {
return Integer.MAX_VALUE; // Run last
}
}
12. Application Configuration
# application.yml server: port: 8443 ssl: enabled: true key-store: classpath:keystore.p12 key-store-password: changeit key-store-type: PKCS12 spring: application: name: secure-api-gateway cloud: gateway: httpclient: connect-timeout: 5000 response-timeout: 10s pool: type: elastic max-idle-time: 60000 httpserver: netty: connection-timeout: 5000 discovery: locator: enabled: true lower-case-service-id: true security: oauth2: resourceserver: jwt: issuer-uri: https://auth.example.com jwk-set-uri: https://auth.example.com/oauth2/jwks data: redis: host: localhost port: 6379 timeout: 2000ms zipkin: base-url: http://zipkin:9411 gateway: ip-whitelist: "10.0.0.0/8,172.16.0.0/12,192.168.0.0/16" rate-limit: default-capacity: 10 default-refill: 10 default-period: PT1M security: jwt: public-key: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEA... issuer: https://auth.example.com resilience4j: circuitbreaker: instances: user-service: sliding-window-size: 10 failure-rate-threshold: 50 wait-duration-in-open-state: 30s permitted-number-of-calls-in-half-open-state: 3 management: endpoints: web: exposure: include: health,info,metrics,prometheus metrics: export: prometheus: enabled: true tracing: sampling: probability: 1.0 logging: level: com.security.gateway: DEBUG org.springframework.cloud.gateway: INFO org.springframework.security: INFO
Security Best Practices
1. Defense in Depth
public class DefenseInDepth {
// Multiple security layers
// 1. IP Whitelist
// 2. Rate Limiting
// 3. Authentication (JWT/API Key)
// 4. Authorization (RBAC)
// 5. Request Validation
// 6. Response Sanitization
}
2. Secrets Management
@Component
public class SecretsManager {
@Value("${encryption.key}")
private String encryptionKey;
public String decryptSecret(String encryptedSecret) {
// Use proper decryption
// Never log secrets
// Use external vault in production
}
}
3. Audit Logging
@Aspect
@Component
public class AuditLogger {
@Around("@annotation(Audited)")
public Object audit(ProceedingJoinPoint pjp) throws Throwable {
// Log all security-relevant events
// Store in secure, immutable audit log
// Include timestamp, client info, action, result
}
}
4. Security Headers
@Component
public class SecurityHeadersFilter implements GlobalFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, GatewayFilterChain chain) {
exchange.getResponse().getHeaders().add("X-Content-Type-Options", "nosniff");
exchange.getResponse().getHeaders().add("X-Frame-Options", "DENY");
exchange.getResponse().getHeaders().add("X-XSS-Protection", "1; mode=block");
exchange.getResponse().getHeaders().add("Strict-Transport-Security",
"max-age=31536000; includeSubDomains; preload");
exchange.getResponse().getHeaders().add("Content-Security-Policy",
"default-src 'self'");
exchange.getResponse().getHeaders().add("Referrer-Policy", "strict-origin-when-cross-origin");
return chain.filter(exchange);
}
}
Performance Tuning
# Performance configuration spring: cloud: gateway: httpclient: connect-timeout: 3000 response-timeout: 5s pool: max-connections: 1000 acquire-timeout: 45000 threads: prefix: gateway daemon: true core-size: 50 max-size: 200 queue-capacity: 10000
Conclusion
This comprehensive Secure API Gateway implementation provides:
Security Features
- Multiple authentication methods (JWT, OAuth2, API Keys)
- Rate limiting (local and distributed)
- IP whitelisting/blacklisting
- Request validation (SQL injection, XSS protection)
- Response sanitization (remove sensitive data)
- Circuit breaking for fault tolerance
- TLS termination for encrypted communication
- Security headers (CSP, HSTS, etc.)
Observability
- Metrics collection (Prometheus)
- Distributed tracing (Zipkin)
- Request logging with correlation IDs
- Health checks and monitoring
Performance
- Reactive non-blocking architecture
- Connection pooling for backend services
- Response caching for static content
- Request/response compression
Operational Features
- Service discovery integration
- Dynamic routing configuration
- Canary deployments support
- A/B testing capabilities
This implementation provides a production-ready secure API gateway suitable for microservices architectures requiring enterprise-grade security, observability, and resilience.
Java Programming Basics – Variables, Loops, Methods, Classes, Files & Exception Handling (Related to Java Programming)
Variables and Data Types in Java:
This topic explains how variables store data in Java and how data types define the kind of values a variable can hold, such as numbers, characters, or text. Java includes primitive types like int, double, and boolean, which are essential for storing and managing data in programs. (GeeksforGeeks)
Read more: https://macronepal.com/blog/variables-and-data-types-in-java/
Basic Input and Output in Java:
This lesson covers how Java programs receive input from users and display output using tools like Scanner for input and System.out.println() for output. These operations allow interaction between the program and the user.
Read more: https://macronepal.com/blog/basic-input-output-in-java/
Arithmetic Operations in Java:
This guide explains mathematical operations such as addition, subtraction, multiplication, and division using operators like +, -, *, and /. These operations are used to perform calculations in Java programs.
Read more: https://macronepal.com/blog/arithmetic-operations-in-java/
If-Else Statement in Java:
The if-else statement allows programs to make decisions based on conditions. It helps control program flow by executing different blocks of code depending on whether a condition is true or false.
Read more: https://macronepal.com/blog/if-else-statement-in-java/
For Loop in Java:
A for loop is used to repeat a block of code a specific number of times. It is commonly used when the number of repetitions is known in advance.
Read more: https://macronepal.com/blog/for-loop-in-java/
Method Overloading in Java:
Method overloading allows multiple methods to have the same name but different parameters. It improves code readability and flexibility by allowing similar tasks to be handled using one method name.
Read more: https://macronepal.com/blog/method-overloading-in-java-a-complete-guide/
Basic Inheritance in Java:
Inheritance is an object-oriented concept that allows one class to inherit properties and methods from another class. It promotes code reuse and helps build hierarchical class structures.
Read more: https://macronepal.com/blog/basic-inheritance-in-java-a-complete-guide/
File Writing in Java:
This topic explains how to create and write data into files using Java. File writing is commonly used to store program data permanently.
Read more: https://macronepal.com/blog/file-writing-in-java-a-complete-guide/
File Reading in Java:
File reading allows Java programs to read stored data from files. It is useful for retrieving saved information and processing it inside applications.
Read more: https://macronepal.com/blog/file-reading-in-java-a-complete-guide/
Exception Handling in Java:
Exception handling helps manage runtime errors using tools like try, catch, and finally. It prevents programs from crashing and allows safe error handling.
Read more: https://macronepal.com/blog/exception-handling-in-java-a-complete-guide/
Constructors in Java:
Constructors are special methods used to initialize objects when they are created. They help assign initial values to object variables automatically.
Read more: https://macronepal.com/blog/constructors-in-java/
Classes and Objects in Java:
Classes are blueprints used to create objects, while objects are instances of classes. These concepts form the foundation of object-oriented programming in Java.
Read more: https://macronepal.com/blog/classes-and-object-in-java/
Methods in Java:
Methods are blocks of code that perform specific tasks. They help organize programs into smaller reusable sections and improve code readability.
Read more: https://macronepal.com/blog/methods-in-java/
Arrays in Java:
Arrays store multiple values of the same type in a single variable. They are useful for handling lists of data such as numbers or names.
Read more: https://macronepal.com/blog/arrays-in-java/
While Loop in Java:
A while loop repeats a block of code as long as a given condition remains true. It is useful when the number of repetitions is not known beforehand.
Read more: https://macronepal.com/blog/while-loop-in-java/