Defense In Depth
Definition

Security Layers

Application Security Layers
// Layer 1: Input Validation (Edge)
@RestController
public class UserController {
@PostMapping("/users")
public User createUser(@Valid @RequestBody UserRequest request) {
// Annotations validate input format
return userService.create(request);
}
}
public class UserRequest {
@NotBlank
@Size(min = 2, max = 50)
@Pattern(regexp = "^[a-zA-Z0-9]+$")
private String username;
@NotBlank
@Email
private String email;
}
// Layer 2: Authentication (Who)
@Configuration
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) {
return http
.oauth2ResourceServer(oauth2 -> oauth2.jwt())
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.build();
}
}
// Layer 3: Authorization (What)
@Service
public class DocumentService {
@PreAuthorize("hasRole('ADMIN') or @accessChecker.canAccess(#docId)")
public Document getDocument(String docId) {
return documentRepository.findById(docId);
}
}
@Component
public class AccessChecker {
public boolean canAccess(String docId, Authentication auth) {
// Check ownership, team membership, explicit sharing
Document doc = documentRepository.findById(docId);
return doc.getOwnerId().equals(auth.getName()) ||
doc.getSharedWith().contains(auth.getName());
}
}
// Layer 4: Data Protection
@Entity
public class User {
@Convert(converter = EncryptedStringConverter.class)
private String ssn; // Encrypted at rest
@JsonIgnore
private String passwordHash; // Never exposed
}
Network Defense Layers

Principle of Least Privilege
// Defense in Depth includes limiting access at every layer
// Database: Different users for different operations
// Read-only user for reporting
CREATE USER 'reporting'@'%' WITH PASSWORD 'xxx';
GRANT SELECT ON app.orders TO 'reporting';
// Limited write user for application
CREATE USER 'app'@'%' WITH PASSWORD 'xxx';
GRANT SELECT, INSERT, UPDATE ON app.orders TO 'app';
-- No DELETE, no schema changes
// Admin user for migrations only
CREATE USER 'admin'@'%' WITH PASSWORD 'xxx';
GRANT ALL ON app.* TO 'admin';
-- Only used by CI/CD, not application
// Application: Role-based access
public class OrderService {
@PreAuthorize("hasRole('CUSTOMER')")
public Order createOrder(OrderRequest request) { }
@PreAuthorize("hasRole('CUSTOMER')")
public Order getOrder(String orderId) {
Order order = orderRepo.findById(orderId);
// Additional check: only own orders
if (!order.getCustomerId().equals(getCurrentUserId())) {
throw new AccessDeniedException("Not your order");
}
return order;
}
@PreAuthorize("hasRole('ADMIN')")
public List<Order> getAllOrders() { }
@PreAuthorize("hasRole('ADMIN')")
public void deleteOrder(String orderId) { }
}
// API Keys: Scoped permissions
{
"apiKey": "sk_live_xxx",
"permissions": ["orders:read", "orders:create"],
"rateLimits": { "requests": 1000, "period": "hour" },
"ipWhitelist": ["203.0.113.0/24"]
}
Redundant Controls

Monitoring and Detection
// Detection layer - know when defenses are probed/breached
@Component
public class SecurityEventListener {
@EventListener
public void onAuthenticationFailure(AuthenticationFailureEvent event) {
String ip = extractIp(event);
String username = event.getAuthentication().getName();
// Log for analysis
securityLog.warn("Auth failure: user={}, ip={}", username, ip);
// Detect brute force
if (failureTracker.recordFailure(ip) > 10) {
securityAlerts.send(
"Possible brute force attack from IP: " + ip);
// Temporary IP block
ipBlocker.block(ip, Duration.ofHours(1));
}
}
@EventListener
public void onAccessDenied(AuthorizationFailureEvent event) {
// Log authorization failures
securityLog.warn("Access denied: user={}, resource={}",
event.getAuthentication().getName(),
event.getSource());
// Detect privilege escalation attempts
if (suspiciousPattern(event)) {
securityAlerts.send("Possible privilege escalation attempt");
}
}
}
// Audit trail - who did what when
@Aspect
@Component
public class AuditAspect {
@AfterReturning("@annotation(Audited)")
public void auditAction(JoinPoint jp) {
AuditEntry entry = AuditEntry.builder()
.user(SecurityContext.getCurrentUser())
.action(jp.getSignature().getName())
.resource(extractResource(jp))
.timestamp(Instant.now())
.outcome("SUCCESS")
.build();
auditRepository.save(entry);
}
}
Tips & Tricks
