NGINX App Protect in Java

Introduction

NGINX App Protect is a web application firewall (WAF) that provides robust security for Java applications. This comprehensive guide covers integrating NGINX App Protect with Java applications, configuring security policies, implementing custom rules, and monitoring security events.

NGINX App Protect Configuration

Basic NGINX Configuration with App Protect

# nginx.conf
http {
# App Protect Configuration
app_protect_enable on;
app_protect_policy_file "/etc/nginx/policies/security-policy.json";
app_protect_security_log_enable on;
app_protect_security_log "/etc/nginx/logs/security-log.json" syslog:server=127.0.0.1:515;
# Upstream Java application
upstream java_backend {
server localhost:8080;
server localhost:8081;
}
server {
listen 80;
server_name myapp.company.com;
# App Protect location
location / {
app_protect_enable on;
app_protect_policy_file "/etc/nginx/policies/security-policy.json";
app_protect_security_log_enable on;
proxy_pass http://java_backend;
proxy_set_header Host $host;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
# Timeout configurations
proxy_connect_timeout 30s;
proxy_read_timeout 30s;
proxy_send_timeout 30s;
}
# Health check endpoint (bypass App Protect)
location /health {
access_log off;
app_protect_enable off;
proxy_pass http://java_backend/actuator/health;
}
# Metrics endpoint (bypass App Protect)
location /metrics {
app_protect_enable off;
proxy_pass http://java_backend/actuator/metrics;
}
}
# API Gateway configuration
server {
listen 443 ssl;
server_name api.company.com;
ssl_certificate /etc/ssl/certs/company.crt;
ssl_certificate_key /etc/ssl/private/company.key;
location /api/ {
app_protect_enable on;
app_protect_policy_file "/etc/nginx/policies/api-policy.json";
proxy_pass http://java_backend/api/;
proxy_set_header X-API-Version v1;
# Rate limiting
limit_req zone=api burst=10 nodelay;
}
}
}

Security Policy Configuration

{
"policy": {
"name": "java-app-security-policy",
"template": {
"name": "POLICY_TEMPLATE_NGINX_BASE"
},
"applicationLanguage": "utf-8",
"enforcementMode": "blocking",
"blocking-settings": {
"violations": [
{
"name": "VIOL_ATTACK_SIGNATURE",
"alarm": true,
"block": true
},
{
"name": "VIOL_FILE_TYPE",
"alarm": true,
"block": true
},
{
"name": "VIOL_PARAMETER_VALUE_METACHAR",
"alarm": true,
"block": true
}
]
},
"signature-settings": {
"signatureStaging": false,
"minimumAccuracyForAutoAddedSignatures": "medium"
},
"file-type": {
"allowed": [
{
"name": "jpg",
"type": "image"
},
{
"name": "png",
"type": "image"
},
{
"name": "pdf",
"type": "document"
}
],
"checkUrlLength": false,
"urlLength": 2048,
"checkPostDataLength": true,
"postDataLength": 4096
},
"method": {
"allowedMethods": ["GET", "POST", "PUT", "DELETE", "HEAD", "OPTIONS"]
},
"parameter": {
"allowedMetacharacters": "",
"illegalMetacharacters": "<>#&`'\\\"",
"checkMaximumParameterValueLength": true,
"maximumParameterValueLength": 10000
},
"url": {
"allowedMetacharacters": "",
"checkMaximumUrlLength": true,
"maximumUrlLength": 2048
},
"header": {
"allowedMetacharacters": "",
"checkHeaderLength": true,
"maximumHeaderLength": 10240
},
"cookie": {
"allowedMetacharacters": "",
"enforcementType": "allow"
},
"data-guard": {
"enabled": true,
"maskData": true,
"creditCardNumbers": true,
"usSocialSecurityNumbers": true
},
"redaction": {
"enabled": true,
"creditCardNumbers": true,
"usSocialSecurityNumbers": true
}
}
}

Java Application Integration

Spring Boot Security Configuration

package com.company.security.nginx;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.web.servlet.config.annotation.CorsRegistry;
import org.springframework.web.servlet.config.annotation.WebMvcConfigurer;
@Configuration
@EnableWebSecurity
public class NginxAppProtectSecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.csrf(csrf -> csrf.disable()) // Handled by NGINX App Protect
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'")
)
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000)
)
.frameOptions(frame -> frame.sameOrigin())
)
.authorizeHttpRequests(authz -> authz
.requestMatchers("/public/**").permitAll()
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasRole("USER")
.anyRequest().authenticated()
)
.formLogin(form -> form.disable()) // Using JWT or API tokens
.httpBasic(basic -> basic.disable()); // Using JWT or API tokens
return http.build();
}
@Bean
public WebMvcConfigurer corsConfigurer() {
return new WebMvcConfigurer() {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/api/**")
.allowedOrigins("https://myapp.company.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("*")
.allowCredentials(true);
}
};
}
}

Request/Response Filter for Security Headers

package com.company.security.nginx;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
@Component
public class SecurityHeadersFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, 
HttpServletResponse response, 
FilterChain filterChain) 
throws ServletException, IOException {
// Add security headers
response.setHeader("X-Content-Type-Options", "nosniff");
response.setHeader("X-Frame-Options", "DENY");
response.setHeader("X-XSS-Protection", "1; mode=block");
response.setHeader("Referrer-Policy", "strict-origin-when-cross-origin");
response.setHeader("Feature-Policy", "geolocation 'none'; microphone 'none'; camera 'none'");
// Custom headers for NGINX App Protect integration
response.setHeader("X-Security-Status", "protected");
response.setHeader("X-WAF-Engine", "NGINX-App-Protect");
filterChain.doFilter(request, response);
}
}

Advanced Security Integration

Custom Security Event Handler

package com.company.security.nginx;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.IOException;
import java.net.HttpURLConnection;
import java.net.URL;
import java.time.LocalDateTime;
import java.util.HashMap;
import java.util.Map;
import java.util.concurrent.BlockingQueue;
import java.util.concurrent.LinkedBlockingQueue;
@Service
public class AppProtectSecurityService {
private static final Logger logger = LoggerFactory.getLogger(AppProtectSecurityService.class);
private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY");
private final ObjectMapper objectMapper;
private final BlockingQueue<SecurityEvent> eventQueue;
private final String appProtectLogUrl;
private final boolean enableRealTimeMonitoring;
public AppProtectSecurityService(ObjectMapper objectMapper,
@Value("${app.protect.log.url}") String appProtectLogUrl,
@Value("${app.protect.monitoring.enabled:false}") boolean enableRealTimeMonitoring) {
this.objectMapper = objectMapper;
this.appProtectLogUrl = appProtectLogUrl;
this.enableRealTimeMonitoring = enableRealTimeMonitoring;
this.eventQueue = new LinkedBlockingQueue<>(1000);
if (enableRealTimeMonitoring) {
startEventProcessor();
}
}
public void logSecurityEvent(SecurityEvent event) {
try {
securityLogger.warn("SECURITY_EVENT: {}", objectMapper.writeValueAsString(event));
if (enableRealTimeMonitoring) {
eventQueue.offer(event);
}
} catch (Exception e) {
logger.error("Failed to log security event", e);
}
}
public SecurityAnalysis analyzeRequest(HttpServletRequest request) {
SecurityAnalysis analysis = new SecurityAnalysis();
// Analyze headers
analyzeHeaders(request, analysis);
// Analyze parameters
analyzeParameters(request, analysis);
// Analyze user agent
analyzeUserAgent(request, analysis);
return analysis;
}
public void handleBlockedRequest(HttpServletRequest request, String violation) {
SecurityEvent event = SecurityEvent.builder()
.timestamp(LocalDateTime.now())
.clientIp(getClientIp(request))
.requestUrl(request.getRequestURL().toString())
.method(request.getMethod())
.userAgent(request.getHeader("User-Agent"))
.violation(violation)
.severity("HIGH")
.action("BLOCKED")
.build();
logSecurityEvent(event);
// Send to security information and event management (SIEM) system
sendToSIEM(event);
}
private void analyzeHeaders(HttpServletRequest request, SecurityAnalysis analysis) {
Map<String, String> suspiciousHeaders = new HashMap<>();
// Check for suspicious headers
String userAgent = request.getHeader("User-Agent");
if (userAgent != null && containsSuspiciousPatterns(userAgent)) {
suspiciousHeaders.put("User-Agent", userAgent);
analysis.addSuspicion("Suspicious User-Agent detected");
}
String contentType = request.getHeader("Content-Type");
if (contentType != null && !isValidContentType(contentType)) {
suspiciousHeaders.put("Content-Type", contentType);
analysis.addSuspicion("Invalid Content-Type header");
}
analysis.setSuspiciousHeaders(suspiciousHeaders);
}
private void analyzeParameters(HttpServletRequest request, SecurityAnalysis analysis) {
Map<String, String[]> parameters = request.getParameterMap();
Map<String, String> suspiciousParameters = new HashMap<>();
for (Map.Entry<String, String[]> entry : parameters.entrySet()) {
String paramName = entry.getKey();
String[] values = entry.getValue();
for (String value : values) {
if (containsInjectionPatterns(value)) {
suspiciousParameters.put(paramName, value);
analysis.addSuspicion("Potential injection in parameter: " + paramName);
}
}
}
analysis.setSuspiciousParameters(suspiciousParameters);
}
private void analyzeUserAgent(HttpServletRequest request, SecurityAnalysis analysis) {
String userAgent = request.getHeader("User-Agent");
if (userAgent != null) {
UserAgentAnalysis uaAnalysis = new UserAgentAnalysis(userAgent);
analysis.setUserAgentAnalysis(uaAnalysis);
if (uaAnalysis.isBot() && !uaAnalysis.isKnownBot()) {
analysis.addSuspicion("Potential malicious bot detected");
}
}
}
private boolean containsSuspiciousPatterns(String input) {
String[] patterns = {
"sqlmap", "nmap", "metasploit", "burp", "w3af", 
"acunetix", "appscan", "nikto", "havij"
};
String lowerInput = input.toLowerCase();
for (String pattern : patterns) {
if (lowerInput.contains(pattern)) {
return true;
}
}
return false;
}
private boolean containsInjectionPatterns(String input) {
String[] patterns = {
"<script", "javascript:", "onload=", "onerror=", 
"union select", "1=1", "or 1=1", "drop table",
"document.cookie", "eval(", "alert("
};
String lowerInput = input.toLowerCase();
for (String pattern : patterns) {
if (lowerInput.contains(pattern)) {
return true;
}
}
return false;
}
private boolean isValidContentType(String contentType) {
String[] validTypes = {
"application/json", "application/x-www-form-urlencoded",
"multipart/form-data", "text/plain"
};
for (String validType : validTypes) {
if (contentType.startsWith(validType)) {
return true;
}
}
return false;
}
private String getClientIp(HttpServletRequest request) {
String xForwardedFor = request.getHeader("X-Forwarded-For");
if (xForwardedFor != null && !xForwardedFor.isEmpty()) {
return xForwardedFor.split(",")[0].trim();
}
return request.getRemoteAddr();
}
private void sendToSIEM(SecurityEvent event) {
try {
URL url = new URL(appProtectLogUrl);
HttpURLConnection conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type", "application/json");
conn.setDoOutput(true);
String jsonPayload = objectMapper.writeValueAsString(event);
conn.getOutputStream().write(jsonPayload.getBytes());
int responseCode = conn.getResponseCode();
if (responseCode != 200) {
logger.warn("Failed to send security event to SIEM. Response code: {}", responseCode);
}
} catch (IOException e) {
logger.error("Error sending security event to SIEM", e);
}
}
private void startEventProcessor() {
Thread processorThread = new Thread(() -> {
while (true) {
try {
SecurityEvent event = eventQueue.take();
processSecurityEvent(event);
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
break;
} catch (Exception e) {
logger.error("Error processing security event", e);
}
}
});
processorThread.setDaemon(true);
processorThread.setName("Security-Event-Processor");
processorThread.start();
}
private void processSecurityEvent(SecurityEvent event) {
// Real-time processing of security events
// Could include:
// - Rate limiting analysis
// - IP reputation checking
// - Pattern detection
// - Alert triggering
logger.info("Processing security event: {} from {}", 
event.getViolation(), event.getClientIp());
}
}
// Domain Models
class SecurityEvent {
private LocalDateTime timestamp;
private String clientIp;
private String requestUrl;
private String method;
private String userAgent;
private String violation;
private String severity;
private String action;
private Map<String, Object> additionalInfo;
// Builder pattern
public static Builder builder() {
return new Builder();
}
// Getters and setters...
public static class Builder {
private SecurityEvent event = new SecurityEvent();
public Builder timestamp(LocalDateTime timestamp) {
event.timestamp = timestamp;
return this;
}
public Builder clientIp(String clientIp) {
event.clientIp = clientIp;
return this;
}
public Builder requestUrl(String requestUrl) {
event.requestUrl = requestUrl;
return this;
}
public Builder method(String method) {
event.method = method;
return this;
}
public Builder userAgent(String userAgent) {
event.userAgent = userAgent;
return this;
}
public Builder violation(String violation) {
event.violation = violation;
return this;
}
public Builder severity(String severity) {
event.severity = severity;
return this;
}
public Builder action(String action) {
event.action = action;
return this;
}
public Builder additionalInfo(Map<String, Object> additionalInfo) {
event.additionalInfo = additionalInfo;
return this;
}
public SecurityEvent build() {
return event;
}
}
}
class SecurityAnalysis {
private Map<String, String> suspiciousHeaders = new HashMap<>();
private Map<String, String> suspiciousParameters = new HashMap<>();
private UserAgentAnalysis userAgentAnalysis;
private List<String> suspicions = new ArrayList<>();
private int riskScore = 0;
public void addSuspicion(String suspicion) {
suspicions.add(suspicion);
riskScore += 10; // Increase risk score for each suspicion
}
// Getters and setters...
}
class UserAgentAnalysis {
private final String userAgent;
private boolean isBot = false;
private boolean isKnownBot = false;
private String browser;
private String os;
public UserAgentAnalysis(String userAgent) {
this.userAgent = userAgent;
analyze();
}
private void analyze() {
if (userAgent == null) return;
String lowerUA = userAgent.toLowerCase();
// Check for bots
isBot = lowerUA.contains("bot") || 
lowerUA.contains("crawler") || 
lowerUA.contains("spider");
// Known legitimate bots
isKnownBot = lowerUA.contains("googlebot") || 
lowerUA.contains("bingbot") ||
lowerUA.contains("slurp");
// Browser detection
if (lowerUA.contains("chrome")) browser = "Chrome";
else if (lowerUA.contains("firefox")) browser = "Firefox";
else if (lowerUA.contains("safari")) browser = "Safari";
else browser = "Unknown";
// OS detection
if (lowerUA.contains("windows")) os = "Windows";
else if (lowerUA.contains("mac")) os = "macOS";
else if (lowerUA.contains("linux")) os = "Linux";
else os = "Unknown";
}
// Getters...
}

Custom Security Policies and Rules

Dynamic Policy Management

package com.company.security.nginx.policy;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service;
import java.io.File;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.ArrayList;
import java.util.List;
import java.util.Map;
@Service
public class DynamicPolicyManager {
private final ObjectMapper objectMapper;
private final String policyDirectory;
private final PolicyReloadService reloadService;
public DynamicPolicyManager(ObjectMapper objectMapper,
@Value("${app.protect.policy.directory}") String policyDirectory,
PolicyReloadService reloadService) {
this.objectMapper = objectMapper;
this.policyDirectory = policyDirectory;
this.reloadService = reloadService;
}
public void createCustomPolicy(String policyName, CustomPolicyConfig config) 
throws PolicyException {
try {
PolicyTemplate policy = loadBasePolicy();
// Apply custom configurations
applyCustomSettings(policy, config);
// Save the policy
String policyContent = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(policy);
Path policyPath = Paths.get(policyDirectory, policyName + ".json");
Files.write(policyPath, policyContent.getBytes());
logger.info("Created custom policy: {}", policyName);
} catch (IOException e) {
throw new PolicyException("Failed to create policy: " + policyName, e);
}
}
public void updatePolicyViolations(String policyName, List<ViolationConfig> violations) 
throws PolicyException {
try {
PolicyTemplate policy = loadPolicy(policyName);
// Update violations
policy.getPolicy().getBlockingSettings().setViolations(violations);
savePolicy(policyName, policy);
reloadService.reloadPolicy(policyName);
} catch (IOException e) {
throw new PolicyException("Failed to update policy violations", e);
}
}
public void addWhitelistEntry(String policyName, WhitelistEntry entry) 
throws PolicyException {
try {
PolicyTemplate policy = loadPolicy(policyName);
// Add to whitelist
if (policy.getPolicy().getWhitelist() == null) {
policy.getPolicy().setWhitelist(new ArrayList<>());
}
policy.getPolicy().getWhitelist().add(entry);
savePolicy(policyName, policy);
reloadService.reloadPolicy(policyName);
} catch (IOException e) {
throw new PolicyException("Failed to add whitelist entry", e);
}
}
public void createRateLimitPolicy(String policyName, RateLimitConfig config) 
throws PolicyException {
try {
PolicyTemplate policy = loadBasePolicy();
// Configure rate limiting
configureRateLimiting(policy, config);
savePolicy(policyName, policy);
} catch (IOException e) {
throw new PolicyException("Failed to create rate limit policy", e);
}
}
public PolicyStatus getPolicyStatus(String policyName) throws PolicyException {
try {
// Check if policy file exists and is valid
Path policyPath = Paths.get(policyDirectory, policyName + ".json");
if (!Files.exists(policyPath)) {
return PolicyStatus.NOT_FOUND;
}
PolicyTemplate policy = loadPolicy(policyName);
return new PolicyStatus(policyName, true, policy.getPolicy().getEnforcementMode());
} catch (IOException e) {
throw new PolicyException("Failed to get policy status", e);
}
}
private PolicyTemplate loadBasePolicy() throws IOException {
Path basePolicyPath = Paths.get(policyDirectory, "base-policy.json");
return objectMapper.readValue(basePolicyPath.toFile(), PolicyTemplate.class);
}
private PolicyTemplate loadPolicy(String policyName) throws IOException {
Path policyPath = Paths.get(policyDirectory, policyName + ".json");
return objectMapper.readValue(policyPath.toFile(), PolicyTemplate.class);
}
private void savePolicy(String policyName, PolicyTemplate policy) throws IOException {
Path policyPath = Paths.get(policyDirectory, policyName + ".json");
String policyContent = objectMapper.writerWithDefaultPrettyPrinter()
.writeValueAsString(policy);
Files.write(policyPath, policyContent.getBytes());
}
private void applyCustomSettings(PolicyTemplate policy, CustomPolicyConfig config) {
// Apply various custom settings based on configuration
if (config.getMaxUrlLength() != null) {
policy.getPolicy().getUrl().setMaximumUrlLength(config.getMaxUrlLength());
}
if (config.getAllowedMethods() != null) {
policy.getPolicy().getMethod().setAllowedMethods(config.getAllowedMethods());
}
if (config.isEnableDataGuard() != null) {
policy.getPolicy().getDataGuard().setEnabled(config.isEnableDataGuard());
}
}
private void configureRateLimiting(PolicyTemplate policy, RateLimitConfig config) {
// Configure rate limiting settings in the policy
// This would depend on the specific policy structure for rate limiting
}
}
// Policy Domain Models
class PolicyTemplate {
private Policy policy;
// Getters and setters...
}
class Policy {
private String name;
private Object template;
private String applicationLanguage;
private String enforcementMode;
private BlockingSettings blockingSettings;
private UrlSettings url;
private MethodSettings method;
private DataGuardSettings dataGuard;
private List<WhitelistEntry> whitelist;
// Getters and setters...
}
class CustomPolicyConfig {
private Integer maxUrlLength;
private List<String> allowedMethods;
private Boolean enableDataGuard;
private List<ViolationConfig> violations;
// Getters and setters...
}
class ViolationConfig {
private String name;
private boolean alarm;
private boolean block;
// Getters and setters...
}
class WhitelistEntry {
private String name;
private String value;
private String type; // "url", "parameter", "ip"
// Getters and setters...
}
class RateLimitConfig {
private int requestsPerMinute;
private int burstSize;
private String zone;
// Getters and setters...
}
class PolicyStatus {
private final String name;
private final boolean active;
private final String enforcementMode;
public PolicyStatus(String name, boolean active, String enforcementMode) {
this.name = name;
this.active = active;
this.enforcementMode = enforcementMode;
}
// Getters...
}
class PolicyException extends Exception {
public PolicyException(String message) {
super(message);
}
public PolicyException(String message, Throwable cause) {
super(message, cause);
}
}

Monitoring and Analytics

Security Dashboard Integration

package com.company.security.nginx.monitoring;
import io.micrometer.core.instrument.Counter;
import io.micrometer.core.instrument.MeterRegistry;
import io.micrometer.core.instrument.Timer;
import org.springframework.stereotype.Component;
import java.time.LocalDateTime;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.TimeUnit;
@Component
public class SecurityMetricsCollector {
private final MeterRegistry meterRegistry;
private final Map<String, Counter> violationCounters;
private final Timer requestProcessingTimer;
private final Counter blockedRequestsCounter;
private final Counter totalRequestsCounter;
public SecurityMetricsCollector(MeterRegistry meterRegistry) {
this.meterRegistry = meterRegistry;
this.violationCounters = new ConcurrentHashMap<>();
this.requestProcessingTimer = Timer.builder("app_protect.request.processing.time")
.description("Time taken to process requests through App Protect")
.register(meterRegistry);
this.blockedRequestsCounter = Counter.builder("app_protect.requests.blocked")
.description("Number of requests blocked by App Protect")
.register(meterRegistry);
this.totalRequestsCounter = Counter.builder("app_protect.requests.total")
.description("Total number of requests processed by App Protect")
.register(meterRegistry);
}
public void recordRequest(String clientIp, String method, String path, long processingTimeMs) {
totalRequestsCounter.increment();
requestProcessingTimer.record(processingTimeMs, TimeUnit.MILLISECONDS);
// Record additional metrics
meterRegistry.counter("app_protect.requests.by.method", "method", method).increment();
meterRegistry.counter("app_protect.requests.by.path", "path", normalizePath(path)).increment();
}
public void recordViolation(String violationType, String clientIp, String path) {
String counterName = "app_protect.violations." + violationType.toLowerCase();
violationCounters.computeIfAbsent(counterName, 
key -> Counter.builder(key)
.description("Number of " + violationType + " violations")
.register(meterRegistry)
).increment();
blockedRequestsCounter.increment();
// Record violation details
meterRegistry.counter("app_protect.violations.by.ip", "ip", clientIp).increment();
meterRegistry.counter("app_protect.violations.by.path", "path", normalizePath(path)).increment();
}
public void recordFalsePositive(String violationType, String path) {
meterRegistry.counter("app_protect.false.positives", "type", violationType).increment();
}
public SecurityMetrics getCurrentMetrics() {
return new SecurityMetrics(
(int) totalRequestsCounter.count(),
(int) blockedRequestsCounter.count(),
getViolationCounts(),
LocalDateTime.now()
);
}
private Map<String, Integer> getViolationCounts() {
Map<String, Integer> counts = new HashMap<>();
violationCounters.forEach((key, counter) -> {
String violationType = key.replace("app_protect.violations.", "");
counts.put(violationType, (int) counter.count());
});
return counts;
}
private String normalizePath(String path) {
// Normalize path for metrics (e.g., replace IDs with placeholders)
return path.replaceAll("/\\d+", "/{id}")
.replaceAll("/[a-f0-9-]{36}", "/{uuid}");
}
}
class SecurityMetrics {
private final int totalRequests;
private final int blockedRequests;
private final Map<String, Integer> violationCounts;
private final LocalDateTime timestamp;
public SecurityMetrics(int totalRequests, int blockedRequests,
Map<String, Integer> violationCounts, LocalDateTime timestamp) {
this.totalRequests = totalRequests;
this.blockedRequests = blockedRequests;
this.violationCounts = violationCounts;
this.timestamp = timestamp;
}
public double getBlockRate() {
return totalRequests > 0 ? (double) blockedRequests / totalRequests * 100 : 0;
}
// Getters...
}
// Real-time Alerting Service
@Component
public class SecurityAlertService {
private final SecurityMetricsCollector metricsCollector;
private final NotificationService notificationService;
private final Map<String, LocalDateTime> lastAlertTime = new ConcurrentHashMap<>();
public SecurityAlertService(SecurityMetricsCollector metricsCollector,
NotificationService notificationService) {
this.metricsCollector = metricsCollector;
this.notificationService = notificationService;
}
public void checkForAnomalies() {
SecurityMetrics metrics = metricsCollector.getCurrentMetrics();
// Check block rate anomaly
if (metrics.getBlockRate() > 10.0) { // More than 10% block rate
triggerAlert("HIGH_BLOCK_RATE", 
String.format("High block rate detected: %.2f%%", metrics.getBlockRate()));
}
// Check specific violation spikes
metrics.getViolationCounts().forEach((violationType, count) -> {
if (count > 100) { // More than 100 violations of a specific type
triggerAlert("VIOLATION_SPIKE", 
String.format("Spike in %s violations: %d", violationType, count));
}
});
}
public void triggerAlert(String alertType, String message) {
String alertKey = alertType + "_" + LocalDateTime.now().toLocalDate();
// Prevent duplicate alerts for the same day
if (shouldSendAlert(alertKey)) {
SecurityAlert alert = new SecurityAlert(alertType, message, LocalDateTime.now());
notificationService.sendSecurityAlert(alert);
lastAlertTime.put(alertKey, LocalDateTime.now());
}
}
private boolean shouldSendAlert(String alertKey) {
LocalDateTime lastAlert = lastAlertTime.get(alertKey);
return lastAlert == null || 
lastAlert.isBefore(LocalDateTime.now().minusHours(1));
}
}
class SecurityAlert {
private final String type;
private final String message;
private final LocalDateTime timestamp;
private final String severity;
public SecurityAlert(String type, String message, LocalDateTime timestamp) {
this.type = type;
this.message = message;
this.timestamp = timestamp;
this.severity = determineSeverity(type);
}
private String determineSeverity(String type) {
switch (type) {
case "HIGH_BLOCK_RATE":
return "HIGH";
case "VIOLATION_SPIKE":
return "MEDIUM";
default:
return "LOW";
}
}
// Getters...
}

CI/CD Integration

Automated Policy Testing

package com.company.security.nginx.testing;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.boot.test.web.server.LocalServerPort;
import org.springframework.http.*;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.ExtendWith;
import static org.junit.jupiter.api.Assertions.*;
@ExtendWith(SpringExtension.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class AppProtectSecurityTests {
@LocalServerPort
private int port;
private TestRestTemplate restTemplate = new TestRestTemplate();
@Test
public void testSqlInjectionProtection() {
String url = "http://localhost:" + port + "/api/users";
// SQL injection attempt
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String sqlInjectionPayload = "{\"username\": \"admin' OR '1'='1\", \"password\": \"test\"}";
HttpEntity<String> entity = new HttpEntity<>(sqlInjectionPayload, headers);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, entity, String.class);
// Should be blocked by App Protect
assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
}
@Test
public void testXssProtection() {
String url = "http://localhost:" + port + "/api/comments";
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
String xssPayload = "{\"comment\": \"<script>alert('XSS')</script>\"}";
HttpEntity<String> entity = new HttpEntity<>(xssPayload, headers);
ResponseEntity<String> response = restTemplate.exchange(
url, HttpMethod.POST, entity, String.class);
// Should be blocked by App Protect
assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
}
@Test
public void testPathTraversalProtection() {
String url = "http://localhost:" + port + "/api/files/../../../etc/passwd";
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
// Should be blocked by App Protect
assertEquals(HttpStatus.FORBIDDEN, response.getStatusCode());
}
@Test
public void testValidRequest() {
String url = "http://localhost:" + port + "/api/public/data";
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
// Valid request should pass through
assertTrue(response.getStatusCode().is2xxSuccessful());
}
@Test
public void testRateLimiting() {
String url = "http://localhost:" + port + "/api/limited";
// Make multiple rapid requests
for (int i = 0; i < 20; i++) {
ResponseEntity<String> response = restTemplate.getForEntity(url, String.class);
if (i >= 10) { // After rate limit threshold
assertEquals(HttpStatus.TOO_MANY_REQUESTS, response.getStatusCode());
}
}
}
}

Best Practices and Configuration

Security Configuration

package com.company.security.nginx.config;
import org.springframework.boot.context.properties.ConfigurationProperties;
import org.springframework.context.annotation.Configuration;
import java.util.List;
@Configuration
@ConfigurationProperties(prefix = "app.protect")
@Data
public class AppProtectConfig {
// NGINX Configuration
private boolean enabled = true;
private String policyDirectory = "/etc/nginx/policies";
private String logDirectory = "/var/log/nginx";
// Security Settings
private EnforcementMode enforcementMode = EnforcementMode.BLOCKING;
private boolean enableLogging = true;
private int maxUrlLength = 2048;
private int maxHeaderLength = 10240;
// Monitoring Settings
private boolean enableMetrics = true;
private String metricsEndpoint = "/actuator/app-protect-metrics";
private int alertThreshold = 10; // Percentage block rate for alerts
// Custom Rules
private List<CustomRule> customRules;
private List<WhitelistEntry> whitelist;
// API Security
private boolean enableApiSecurity = true;
private int apiRateLimit = 100; // Requests per minute
private int apiBurstSize = 20;
public enum EnforcementMode {
BLOCKING, TRANSPARENT
}
}
class CustomRule {
private String name;
private String description;
private String pattern;
private RuleAction action;
private String severity;
// Getters and setters...
}
class WhitelistEntry {
private String type; // IP, URL, PARAMETER
private String value;
private String description;
// Getters and setters...
}
enum RuleAction {
BLOCK, ALERT, LOG
}

Integration Checklist

  1. Install NGINX App Protect on your NGINX instances
  2. Configure security policies based on application requirements
  3. Implement custom rules for application-specific protection
  4. Set up logging and monitoring for security events
  5. Integrate with SIEM systems for centralized security monitoring
  6. Configure alerting for security incidents
  7. Implement automated testing for security policies
  8. Establish incident response procedures for security events

Key Benefits

  • Real-time protection against OWASP Top 10 vulnerabilities
  • Positive security model with whitelisting capabilities
  • Custom rule creation for application-specific protection
  • Comprehensive logging and monitoring capabilities
  • Integration with existing security infrastructure

By implementing these NGINX App Protect integration patterns, Java applications can achieve enterprise-grade security with minimal performance impact while maintaining comprehensive visibility and control over web traffic.

Leave a Reply

Your email address will not be published. Required fields are marked *


Macro Nepal Helper