AWS WAF (Web Application Firewall) is a powerful service that protects web applications from common web exploits and bots. When integrated with Java applications, it provides a robust security layer that can filter malicious traffic before it reaches your application servers. This article explores various approaches to programmatically integrate and manage AWS WAF with Java applications.
Understanding AWS WAF Components
Key AWS WAF Concepts:
- Web ACLs: The core container for rules that define protection criteria
- Rules: Individual conditions that match specific patterns in requests
- Rule Groups: Reusable collections of rules
- IP Sets: Collections of IP addresses for allow/block lists
- Rate-based Rules: Automatically block IPs exceeding request thresholds
- Managed Rule Groups: Pre-configured rules from AWS and Marketplace vendors
Integration Architecture:
Client → CloudFront/ALB → AWS WAF → Java Application ↓ Programmatic Management ↓ Java SDK/API Calls
Prerequisites
Before implementing AWS WAF integration, ensure you have:
- AWS Account with WAF permissions
- AWS Credentials configured (Access Key/Secret Key or IAM Role)
- Java 11+ with AWS SDK
- Application Load Balancer or CloudFront Distribution
- AWS WAF-enabled resource
AWS SDK Setup and Dependencies
Maven Dependencies:
<dependencies> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>wafv2</artifactId> <version>2.20.0</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>auth</artifactId> <version>2.20.0</version> </dependency> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>regions</artifactId> <version>2.20.0</version> </dependency> <!-- For enhanced HTTP client --> <dependency> <groupId>software.amazon.awssdk</groupId> <artifactId>url-connection-client</artifactId> <version>2.20.0</version> </dependency> </dependencies>
Gradle:
dependencies {
implementation("software.amazon.awssdk:wafv2:2.20.0")
implementation("software.amazon.awssdk:auth:2.20.0")
implementation("software.amazon.awssdk:regions:2.20.0")
}
AWS WAF Client Configuration
package com.example.awswaf;
import software.amazon.awssdk.auth.credentials.AwsBasicCredentials;
import software.amazon.awssdk.auth.credentials.StaticCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.wafv2.Wafv2Client;
import software.amazon.awssdk.services.wafv2.model.*;
import java.net.URI;
import java.util.List;
public class AwsWafClient {
private final Wafv2Client wafv2Client;
private final String scope = "REGIONAL"; // or "CLOUDFRONT"
public AwsWafClient(String region, String accessKey, String secretKey) {
this.wafv2Client = Wafv2Client.builder()
.region(Region.of(region))
.credentialsProvider(StaticCredentialsProvider.create(
AwsBasicCredentials.create(accessKey, secretKey)))
.build();
}
// For use with IAM roles (recommended for production)
public AwsWafClient(String region) {
this.wafv2Client = Wafv2Client.builder()
.region(Region.of(region))
.build();
}
}
Core WAF Management Operations
1. Creating Web ACLs Programmatically
public class WafManager {
private final Wafv2Client wafv2Client;
private final String scope;
public WafManager(Wafv2Client wafv2Client, String scope) {
this.wafv2Client = wafv2Client;
this.scope = scope;
}
/**
* Create a basic Web ACL with common protection rules
*/
public String createWebAcl(String webAclName, String description,
List<String> allowedIps) throws WafException {
// Create IP Set for allowed IPs
String ipSetArn = null;
if (allowedIps != null && !allowedIps.isEmpty()) {
ipSetArn = createIpSet(webAclName + "-allowed-ips", allowedIps);
}
// Define rules
List<Rule> rules = createDefaultRules(ipSetArn);
CreateWebAclRequest request = CreateWebAclRequest.builder()
.name(webAclName)
.scope(scope)
.defaultAction(AllowAction.builder().build())
.visibilityConfig(VisibilityConfig.builder()
.cloudWatchMetricsEnabled(true)
.metricName(webAclName + "-metrics")
.sampledRequestsEnabled(true)
.build())
.rules(rules)
.description(description)
.build();
CreateWebAclResponse response = wafv2Client.createWebAcl(request);
return response.summary().arn();
}
/**
* Create default security rules
*/
private List<Rule> createDefaultRules(String allowedIpSetArn) {
ImmutableList.Builder<Rule> rules = ImmutableList.builder();
// 1. IP Allow List Rule (if provided)
if (allowedIpSetArn != null) {
Rule ipRule = Rule.builder()
.name("IP-Allow-List")
.priority(0)
.action(BlockAction.builder().build())
.statement(Statement.builder()
.notStatement(NotStatement.builder()
.statement(Statement.builder()
.ipSetReferenceStatement(IpSetReferenceStatement.builder()
.arn(allowedIpSetArn)
.build())
.build())
.build())
.build())
.visibilityConfig(VisibilityConfig.builder()
.cloudWatchMetricsEnabled(true)
.metricName("IP-Allow-List-Metrics")
.sampledRequestsEnabled(true)
.build())
.build();
rules.add(ipRule);
}
// 2. AWS Managed Common Rule Set
Rule managedRule = Rule.builder()
.name("AWSManagedRulesCommonRuleSet")
.priority(1)
.overrideAction(OverrideAction.builder().none(NoneAction.builder().build()).build())
.statement(Statement.builder()
.managedRuleGroupStatement(ManagedRuleGroupStatement.builder()
.vendorName("AWS")
.name("AWSManagedRulesCommonRuleSet")
.build())
.build())
.visibilityConfig(VisibilityConfig.builder()
.cloudWatchMetricsEnabled(true)
.metricName("AWSManagedRulesCommonRuleSet-Metrics")
.sampledRequestsEnabled(true)
.build())
.build();
rules.add(managedRule);
// 3. Rate-based rule for brute force protection
Rule rateRule = Rule.builder()
.name("RateLimitRule")
.priority(2)
.action(BlockAction.builder().build())
.statement(Statement.builder()
.rateBasedStatement(RateBasedStatement.builder()
.limit(2000)
.aggregateKeyType("IP")
.build())
.build())
.visibilityConfig(VisibilityConfig.builder()
.cloudWatchMetricsEnabled(true)
.metricName("RateLimitRule-Metrics")
.sampledRequestsEnabled(true)
.build())
.build();
rules.add(rateRule);
return rules.build();
}
/**
* Create IP Set for allow/block lists
*/
public String createIpSet(String ipSetName, List<String> ipAddresses) throws WafException {
CreateIpSetRequest request = CreateIpSetRequest.builder()
.name(ipSetName)
.scope(scope)
.ipAddressVersion("IPV4")
.addresses(ipAddresses)
.build();
CreateIpSetResponse response = wafv2Client.createIpSet(request);
return response.summary().arn();
}
}
2. Dynamic IP Management
public class DynamicIpManager {
private final Wafv2Client wafv2Client;
private final String scope;
public DynamicIpManager(Wafv2Client wafv2Client, String scope) {
this.wafv2Client = wafv2Client;
this.scope = scope;
}
/**
* Add IP addresses to an existing IP Set
*/
public void addIpsToBlockList(String ipSetArn, List<String> ipAddresses) throws WafException {
// Get current IP set
GetIpSetRequest getRequest = GetIpSetRequest.builder()
.name(getIpSetNameFromArn(ipSetArn))
.scope(scope)
.id(getIpSetIdFromArn(ipSetArn))
.build();
GetIpSetResponse getResponse = wafv2Client.getIpSet(getRequest);
// Merge existing and new IPs
List<String> currentIps = getResponse.ipSet().addresses();
List<String> updatedIps = new ArrayList<>(currentIps);
updatedIps.addAll(ipAddresses);
// Remove duplicates
updatedIps = updatedIps.stream().distinct().collect(Collectors.toList());
// Update IP set
UpdateIpSetRequest updateRequest = UpdateIpSetRequest.builder()
.name(getIpSetNameFromArn(ipSetArn))
.scope(scope)
.id(getIpSetIdFromArn(ipSetArn))
.addresses(updatedIps)
.lockToken(getResponse.lockToken())
.build();
wafv2Client.updateIpSet(updateRequest);
}
/**
* Block IP addresses temporarily (for rate limiting violations)
*/
public void temporarilyBlockIp(String ipSetArn, String ipAddress, int durationMinutes) {
addIpsToBlockList(ipSetArn, List.of(ipAddress));
// Schedule removal after duration
new Timer().schedule(new TimerTask() {
@Override
public void run() {
removeIpsFromBlockList(ipSetArn, List.of(ipAddress));
}
}, durationMinutes * 60 * 1000L);
}
private String getIpSetNameFromArn(String arn) {
// Extract name from ARN: arn:aws:wafv2:region:account:regional/ipset/name/id
String[] parts = arn.split("/");
return parts[parts.length - 2];
}
private String getIpSetIdFromArn(String arn) {
String[] parts = arn.split("/");
return parts[parts.length - 1];
}
}
Real-time Security Monitoring and Response
public class WafSecurityMonitor {
private final Wafv2Client wafv2Client;
private final String webAclArn;
private final Map<String, Integer> ipViolationCount = new ConcurrentHashMap<>();
private final int maxViolationsBeforeBlock = 5;
public WafSecurityMonitor(Wafv2Client wafv2Client, String webAclArn) {
this.wafv2Client = wafv2Client;
this.webAclArn = webAclArn;
}
/**
* Process application-level security events and update WAF rules
*/
public void handleSecurityEvent(SecurityEvent event) {
switch (event.getType()) {
case "BRUTE_FORCE_ATTEMPT":
handleBruteForceAttempt(event.getSourceIp());
break;
case "SQL_INJECTION_ATTEMPT":
handleInjectionAttempt(event.getSourceIp());
break;
case "SCANNER_DETECTED":
handleScannerDetection(event.getSourceIp());
break;
}
}
private void handleBruteForceAttempt(String sourceIp) {
int violationCount = ipViolationCount.getOrDefault(sourceIp, 0) + 1;
ipViolationCount.put(sourceIp, violationCount);
if (violationCount >= maxViolationsBeforeBlock) {
blockIpTemporarily(sourceIp, 30); // Block for 30 minutes
ipViolationCount.remove(sourceIp);
logSecurityAction("Blocked IP due to brute force attempts: " + sourceIp);
}
}
private void blockIpTemporarily(String ipAddress, int minutes) {
try {
// Get the block list IP set ARN from your Web ACL configuration
String blockListArn = getBlockListIpSetArn();
DynamicIpManager ipManager = new DynamicIpManager(wafv2Client, "REGIONAL");
ipManager.temporarilyBlockIp(blockListArn, ipAddress, minutes);
} catch (WafException e) {
logError("Failed to block IP: " + ipAddress, e);
}
}
public static class SecurityEvent {
private final String type;
private final String sourceIp;
private final String userAgent;
private final String path;
private final Instant timestamp;
public SecurityEvent(String type, String sourceIp, String userAgent, String path) {
this.type = type;
this.sourceIp = sourceIp;
this.userAgent = userAgent;
this.path = path;
this.timestamp = Instant.now();
}
// Getters
public String getType() { return type; }
public String getSourceIp() { return sourceIp; }
public String getUserAgent() { return userAgent; }
public String getPath() { return path; }
public Instant getTimestamp() { return timestamp; }
}
}
Spring Boot Integration
package com.example.awswaf.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import software.amazon.awssdk.auth.credentials.DefaultCredentialsProvider;
import software.amazon.awssdk.regions.Region;
import software.amazon.awssdk.services.wafv2.Wafv2Client;
@Configuration
public class AwsWafConfig {
@Value("${aws.region:us-east-1}")
private String awsRegion;
@Bean
public Wafv2Client wafv2Client() {
return Wafv2Client.builder()
.region(Region.of(awsRegion))
.credentialsProvider(DefaultCredentialsProvider.create())
.build();
}
@Bean
public WafManager wafManager(Wafv2Client wafv2Client) {
return new WafManager(wafv2Client, "REGIONAL");
}
@Bean
public WafSecurityMonitor wafSecurityMonitor(Wafv2Client wafv2Client,
@Value("${aws.waf.webacl.arn}") String webAclArn) {
return new WafSecurityMonitor(wafv2Client, webAclArn);
}
}
Spring Controller with WAF Integration:
@RestController
@RequestMapping("/api")
public class SecureController {
private final WafSecurityMonitor wafSecurityMonitor;
private final LoginAttemptService loginAttemptService;
public SecureController(WafSecurityMonitor wafSecurityMonitor,
LoginAttemptService loginAttemptService) {
this.wafSecurityMonitor = wafSecurityMonitor;
this.loginAttemptService = loginAttemptService;
}
@PostMapping("/login")
public ResponseEntity<LoginResponse> login(@RequestBody LoginRequest request,
HttpServletRequest httpRequest) {
String clientIp = getClientIp(httpRequest);
// Check for brute force attempts
if (loginAttemptService.isBlocked(clientIp)) {
wafSecurityMonitor.handleSecurityEvent(
new WafSecurityMonitor.SecurityEvent("BRUTE_FORCE_ATTEMPT",
clientIp, httpRequest.getHeader("User-Agent"), "/api/login"));
return ResponseEntity.status(429).body(new LoginResponse("Too many attempts"));
}
// Authentication logic
boolean authenticated = authenticateUser(request);
if (!authenticated) {
loginAttemptService.recordFailedAttempt(clientIp);
wafSecurityMonitor.handleSecurityEvent(
new WafSecurityMonitor.SecurityEvent("BRUTE_FORCE_ATTEMPT",
clientIp, httpRequest.getHeader("User-Agent"), "/api/login"));
return ResponseEntity.status(401).body(new LoginResponse("Invalid credentials"));
}
loginAttemptService.recordSuccess(clientIp);
return ResponseEntity.ok(new LoginResponse("Login successful"));
}
@PostMapping("/search")
public ResponseEntity<SearchResponse> search(@RequestBody SearchRequest request,
HttpServletRequest httpRequest) {
String clientIp = getClientIp(httpRequest);
// Check for SQL injection patterns
if (containsSqlInjection(request.getQuery())) {
wafSecurityMonitor.handleSecurityEvent(
new WafSecurityMonitor.SecurityEvent("SQL_INJECTION_ATTEMPT",
clientIp, httpRequest.getHeader("User-Agent"), "/api/search"));
return ResponseEntity.badRequest().body(new SearchResponse("Invalid search query"));
}
// Process search
return ResponseEntity.ok(processSearch(request));
}
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 boolean containsSqlInjection(String input) {
// Basic SQL injection detection
String[] sqlPatterns = {"' OR '1'='1", " UNION ", " SELECT ", " DROP ", " INSERT "};
String upperInput = input.toUpperCase();
return Arrays.stream(sqlPatterns).anyMatch(upperInput::contains);
}
}
WAF Metrics and Monitoring
public class WafMetricsService {
private final Wafv2Client wafv2Client;
private final CloudWatchClient cloudWatchClient;
public WafMetricsService(Wafv2Client wafv2Client, CloudWatchClient cloudWatchClient) {
this.wafv2Client = wafv2Client;
this.cloudWatchClient = cloudWatchClient;
}
/**
* Get WAF metrics for monitoring and alerting
*/
public WafMetrics getWebAclMetrics(String webAclName, String metricName,
int periodHours) throws WafException {
Instant endTime = Instant.now();
Instant startTime = endTime.minus(periodHours, ChronoUnit.HOURS);
GetWebAclRequest request = GetWebAclRequest.builder()
.name(webAclName)
.scope("REGIONAL")
.id(getWebAclIdFromName(webAclName))
.build();
GetWebAclResponse response = wafv2Client.getWebAcl(request);
// Get CloudWatch metrics
List<MetricDataResult> metricData = getCloudWatchMetrics(
webAclName, metricName, startTime, endTime);
return new WafMetrics(response.webAcl(), metricData);
}
public static class WafMetrics {
private final WebAcl webAcl;
private final List<MetricDataResult> metricData;
private final Instant timestamp;
public WafMetrics(WebAcl webAcl, List<MetricDataResult> metricData) {
this.webAcl = webAcl;
this.metricData = metricData;
this.timestamp = Instant.now();
}
// Getters and utility methods
public long getTotalBlockedRequests() {
// Calculate from metric data
return metricData.stream()
.mapToLong(result -> result.values().stream().mapToLong(Double::longValue).sum())
.sum();
}
}
}
Automated WAF Deployment Pipeline
public class WafDeploymentPipeline {
public static void main(String[] args) {
try {
// Configuration
String environment = args.length > 0 ? args[0] : "dev";
String region = "us-east-1";
// Initialize clients
Wafv2Client wafv2Client = Wafv2Client.builder()
.region(Region.of(region))
.build();
WafManager wafManager = new WafManager(wafv2Client, "REGIONAL");
// Environment-specific configuration
WafConfig config = loadWafConfig(environment);
// Deploy WAF
String webAclArn = wafManager.createWebAcl(
config.getWebAclName(),
config.getDescription(),
config.getAllowedIps()
);
System.out.println("WAF deployed successfully: " + webAclArn);
// Associate with ALB (if needed)
associateWithAlb(webAclArn, config.getLoadBalancerArn());
} catch (Exception e) {
System.err.println("WAF deployment failed: " + e.getMessage());
System.exit(1);
}
}
public static class WafConfig {
private String webAclName;
private String description;
private List<String> allowedIps;
private String loadBalancerArn;
// Getters and setters
}
}
Best Practices for AWS WAF Integration
- Least Privilege: Use IAM roles with minimal required permissions
- Layered Security: Combine WAF with application-level security
- Monitoring: Implement comprehensive logging and alerting
- Testing: Regularly test WAF rules against your application
- Automation: Use Infrastructure as Code for WAF management
- Cost Optimization: Monitor and optimize rule complexity
Sample IAM Policy:
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"wafv2:GetWebACL",
"wafv2:UpdateWebACL",
"wafv2:CreateIPSet",
"wafv2:UpdateIPSet"
],
"Resource": "*"
}
]
}
Benefits for Java Applications
- Real-time Protection: Block threats before they reach your application
- Dynamic Rules: Adapt security rules based on application behavior
- Reduced Load: Offload security processing from application servers
- Compliance: Meet security standards and compliance requirements
- Cost Savings: Reduce impact of DDoS and malicious traffic
Conclusion
Integrating AWS WAF with Java applications provides a powerful, scalable security layer that protects against a wide range of web application threats. By leveraging the AWS SDK for Java, you can:
- Programmatically manage WAF rules and configurations
- Dynamically update security policies based on application events
- Automate security responses to detected threats
- Monitor and analyze security metrics in real-time
- Integrate seamlessly with Spring Boot and other Java frameworks
This approach enables Java applications to benefit from enterprise-grade security while maintaining the flexibility and control needed for modern application development. By moving security logic to the edge with AWS WAF, you can significantly reduce the attack surface and improve the overall resilience of your Java applications.