Introduction to Configuration Hardening
Configuration hardening involves securing application configurations to minimize attack surfaces and prevent security vulnerabilities. This comprehensive guide covers security best practices for Java applications, including environment-specific configurations, security headers, encryption, and compliance standards.
Core Security Configuration
Application Properties Security
application-security.yml
# Primary security configuration
spring:
application:
name: secure-java-app
# Security: Do not expose build information
build:
info:
enabled: false
# Security: Disable autoconfiguration for sensitive components
autoconfigure:
exclude:
- org.springframework.boot.autoconfigure.jdbc.DataSourceAutoConfiguration
- org.springframework.boot.autoconfigure.orm.jpa.HibernateJpaAutoConfiguration
# Security: Servlet configuration
servlet:
multipart:
max-file-size: 10MB
max-request-size: 10MB
enabled: true
# Security: Jackson configuration
jackson:
default-property-inclusion: non_null
deserialization:
fail-on-unknown-properties: true
parser:
allow-unquoted-control-chars: false
allow-comments: false
visibility:
field: any
getter: none
setter: none
creator: none
# Security: JPA configuration
jpa:
show-sql: false
properties:
hibernate:
format_sql: false
use_sql_comments: false
jdbc:
lob:
non_contextual_creation: true
# Security: Disable automatic DDL generation
hbm2ddl:
auto: validate
# Security: Data source configuration
datasource:
# Security: Use environment variables for credentials
url: ${DB_URL:jdbc:h2:mem:testdb}
username: ${DB_USERNAME:sa}
password: ${DB_PASSWORD:}
hikari:
connection-timeout: 30000
maximum-pool-size: 10
minimum-idle: 2
max-lifetime: 1800000
leak-detection-threshold: 60000
# Security: Mail configuration
mail:
properties:
mail:
smtp:
auth: true
starttls:
enable: true
# Security: Cache configuration
cache:
type: simple
cache-names:
- security-tokens
- rate-limits
caffeine:
spec: maximumSize=1000,expireAfterWrite=300s
# Security: Session configuration
session:
store-type: none
timeout: 1800
servlet:
filter-order: 100
# Security: Web configuration
web:
resources:
add-mappings: false
locale-resolver: fixed
# Security: Messages configuration
messages:
encoding: UTF-8
cache-duration: 3600
# Security: Server configuration
server:
# Security: Server information hiding
server-header: ""
# Security: Port configuration
port: ${SERVER_PORT:8080}
# Security: SSL/TLS configuration
ssl:
enabled: ${SSL_ENABLED:false}
key-store: ${SSL_KEY_STORE:}
key-store-password: ${SSL_KEY_STORE_PASSWORD:}
key-store-type: ${SSL_KEY_STORE_TYPE:PKCS12}
key-alias: ${SSL_KEY_ALIAS:}
protocol: TLS
enabled-protocols: TLSv1.2,TLSv1.3
ciphers: TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384,TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256,TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256
# Security: Servlet configuration
servlet:
context-path: /
session:
cookie:
http-only: true
secure: ${COOKIE_SECURE:true}
max-age: 1800
same-site: strict
name: SESSION
encoding:
charset: UTF-8
enabled: true
force: true
# Security: HTTP configuration
http2:
enabled: true
compression:
enabled: true
mime-types: text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json
min-response-size: 1024
# Security: Hide server version
error:
include-stacktrace: never
include-message: never
include-binding-errors: never
include-exception: false
# Security: Connection timeout
connection-timeout: 30000
# Security: Maximum HTTP header size
max-http-header-size: 8KB
# Security: Logging configuration
logging:
level:
# Security: Reduce sensitive logging
com.example.secure: INFO
org.springframework.security: WARN
org.springframework.web: WARN
org.hibernate: WARN
# Security: Disable sensitive SQL logging
org.hibernate.SQL: ERROR
org.hibernate.type.descriptor.sql.BasicBinder: ERROR
# Security: Log file configuration
file:
name: /var/log/secure-app/application.log
max-size: 10MB
max-history: 30
total-size-cap: 1GB
pattern:
# Security: Avoid logging sensitive information
file: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %mask%msg%n"
console: "%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %mask%msg%n"
# Security: Actuator endpoints configuration
management:
endpoints:
web:
exposure:
# Security: Limit exposed endpoints
include: health,info,metrics,prometheus
exclude: env,beans,configprops,dump,heapdump,threaddump,shutdown
# Security: Custom management port
base-path: /management
enabled-by-default: false
jmx:
exposure:
exclude: "*"
endpoint:
health:
enabled: true
show-details: when_authorized
show-components: when_authorized
probes:
enabled: true
info:
enabled: true
metrics:
enabled: true
prometheus:
enabled: true
# Security: Disable sensitive endpoints
env:
enabled: false
beans:
enabled: false
configprops:
enabled: false
dump:
enabled: false
heapdump:
enabled: false
threaddump:
enabled: false
shutdown:
enabled: false
# Security: Management server configuration
server:
port: ${MANAGEMENT_PORT:8081}
address: 127.0.0.1
ssl:
enabled: true
# Security: Custom application security configuration
app:
security:
# Security: JWT configuration
jwt:
secret: ${JWT_SECRET:}
expiration: 3600
issuer: secure-java-app
audience: secure-users
# Security: CORS configuration
cors:
allowed-origins: ${ALLOWED_ORIGINS:https://localhost:3000,https://example.com}
allowed-methods: GET,POST,PUT,DELETE,OPTIONS
allowed-headers: Authorization,Content-Type,X-Requested-With
allow-credentials: true
max-age: 3600
# Security: Rate limiting
rate-limit:
enabled: true
requests-per-minute: 100
burst-capacity: 20
# Security: Password policy
password:
min-length: 12
require-uppercase: true
require-lowercase: true
require-digits: true
require-special-chars: true
max-age-days: 90
history-size: 5
# Security: Session management
session:
max-sessions-per-user: 1
prevent-session-fixation: true
# Security: Encryption configuration
encryption:
algorithm: AES/GCM/NoPadding
key-size: 256
iv-size: 96
salt-size: 128
# Security: Database encryption
encryption:
enabled: true
algorithm: AES
key: ${ENCRYPTION_KEY:}
salt: ${ENCRYPTION_SALT:}
Environment-Specific Configurations
application-development.yml
# Development environment configuration spring: config: activate: on-profile: development # Security: Development-specific relaxations security: require-ssl: false # Security: Development database datasource: url: jdbc:h2:mem:devdb username: devuser password: devpass hikari: maximum-pool-size: 5 # Security: Development logging jackson: serialization: indent-output: true jpa: show-sql: true properties: hibernate: format_sql: true hbm2ddl: auto: create-drop # Security: Development server server: ssl: enabled: false servlet: session: cookie: secure: false # Security: Development logging logging: level: com.example.secure: DEBUG org.springframework.security: DEBUG org.hibernate.SQL: DEBUG org.hibernate.type.descriptor.sql.BasicBinder: TRACE # Security: Development endpoints management: endpoints: web: exposure: include: "*" endpoint: health: show-details: always env: enabled: true beans: enabled: true app: security: rate-limit: enabled: false
application-production.yml
# Production environment configuration
spring:
config:
activate:
on-profile: production
# Security: Production database
datasource:
url: ${DB_URL}
username: ${DB_USERNAME}
password: ${DB_PASSWORD}
hikari:
maximum-pool-size: 20
minimum-idle: 5
leak-detection-threshold: 120000
# Security: Production JPA
jpa:
properties:
hibernate:
hbm2ddl:
auto: validate
jdbc:
batch_size: 50
order_inserts: true
order_updates: true
# Security: Production cache
cache:
type: redis
redis:
time-to-live: 3600s
cache-null-values: false
# Security: Production server
server:
ssl:
enabled: true
key-store: ${SSL_KEY_STORE}
key-store-password: ${SSL_KEY_STORE_PASSWORD}
servlet:
session:
timeout: 1800
cookie:
secure: true
same-site: strict
# Security: Production logging
logging:
level:
com.example.secure: INFO
org.springframework: WARN
org.hibernate: ERROR
file:
name: /var/log/secure-app/production.log
# Security: Production management
management:
endpoints:
web:
exposure:
include: health,info,metrics
jmx:
enabled: false
endpoint:
health:
show-details: never
app:
security:
rate-limit:
enabled: true
requests-per-minute: 60
burst-capacity: 10
session:
max-sessions-per-user: 1
Security Configuration Classes
Main Security Configuration
package com.example.secure.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.actuate.autoconfigure.security.servlet.EndpointRequest;
import org.springframework.boot.actuate.health.HealthEndpoint;
import org.springframework.boot.actuate.info.InfoEndpoint;
import org.springframework.boot.actuate.metrics.export.prometheus.PrometheusScrapeEndpoint;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.config.annotation.authentication.configuration.AuthenticationConfiguration;
import org.springframework.security.config.annotation.method.configuration.EnableMethodSecurity;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
import org.springframework.security.config.http.SessionCreationPolicy;
import org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.authentication.UsernamePasswordAuthenticationFilter;
import org.springframework.web.cors.CorsConfiguration;
import org.springframework.web.cors.CorsConfigurationSource;
import org.springframework.web.cors.UrlBasedCorsConfigurationSource;
import java.util.Arrays;
import java.util.List;
/**
* Main security configuration with comprehensive hardening
*/
@Configuration
@EnableWebSecurity
@EnableMethodSecurity(
prePostEnabled = true,
securedEnabled = true,
jsr250Enabled = true
)
public class SecurityConfig {
@Value("${app.security.cors.allowed-origins}")
private String[] allowedOrigins;
@Value("${app.security.cors.allowed-methods}")
private String[] allowedMethods;
@Value("${app.security.cors.allowed-headers}")
private String[] allowedHeaders;
@Value("${app.security.cors.allow-credentials}")
private boolean allowCredentials;
@Value("${app.security.cors.max-age}")
private long maxAge;
@Autowired
private JwtAuthenticationFilter jwtAuthenticationFilter;
@Autowired
private SecurityProperties securityProperties;
/**
* Password encoder with secure configuration
*/
@Bean
public PasswordEncoder passwordEncoder() {
return new BCryptPasswordEncoder(12); // High cost factor for better security
}
/**
* Authentication manager
*/
@Bean
public AuthenticationManager authenticationManager(
AuthenticationConfiguration authenticationConfiguration) throws Exception {
return authenticationConfiguration.getAuthenticationManager();
}
/**
* Security filter chain with comprehensive hardening
*/
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
// Disable CSRF for stateless API (handled by JWT)
.csrf(csrf -> csrf.disable())
// CORS configuration
.cors(cors -> cors.configurationSource(corsConfigurationSource()))
// Session management - stateless
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
)
// Authorization rules
.authorizeHttpRequests(authz -> authz
// Public endpoints
.requestMatchers(
"/api/auth/login",
"/api/auth/register",
"/api/public/**",
"/error"
).permitAll()
// Actuator endpoints with role-based access
.requestMatchers(EndpointRequest.to(HealthEndpoint.class)).permitAll()
.requestMatchers(EndpointRequest.to(InfoEndpoint.class)).permitAll()
.requestMatchers(EndpointRequest.to(PrometheusScrapeEndpoint.class)).hasRole("MONITORING")
// All other endpoints require authentication
.anyRequest().authenticated()
)
// Security headers
.headers(headers -> headers
// Content Security Policy
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'; style-src 'self' 'unsafe-inline'; img-src 'self' data:; font-src 'self'; connect-src 'self'; media-src 'self'; object-src 'none'; child-src 'self'; frame-ancestors 'none'; form-action 'self'; base-uri 'self'; manifest-src 'self'")
)
// HTTP Strict Transport Security
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.preload(true)
.maxAgeInSeconds(31536000) // 1 year
)
// Frame options
.frameOptions(frame -> frame
.deny()
)
// XSS Protection
.xssProtection(xss -> xss
.headerValue(org.springframework.security.web.header.writers.XXssProtectionHeaderWriter.HeaderValue.ENABLED_MODE_BLOCK)
)
// Content Type options
.contentTypeOptions(contentType -> contentType
.disable() // Spring Security enables this by default
)
// Referrer Policy
.referrerPolicy(referrer -> referrer
.policy(org.springframework.security.web.header.writers.ReferrerPolicyHeaderWriter.ReferrerPolicy.STRICT_ORIGIN_WHEN_CROSS_ORIGIN)
)
// Permissions Policy
.permissionsPolicy(permissions -> permissions
.policy("geolocation=(), microphone=(), camera=(), payment=()")
)
)
// JWT filter
.addFilterBefore(jwtAuthenticationFilter, UsernamePasswordAuthenticationFilter.class)
// Exception handling
.exceptionHandling(exception -> exception
.authenticationEntryPoint(new JwtAuthenticationEntryPoint())
.accessDeniedHandler(new JwtAccessDeniedHandler())
);
return http.build();
}
/**
* CORS configuration source
*/
@Bean
public CorsConfigurationSource corsConfigurationSource() {
CorsConfiguration configuration = new CorsConfiguration();
// Set allowed origins
configuration.setAllowedOrigins(Arrays.asList(allowedOrigins));
// Set allowed methods
configuration.setAllowedMethods(Arrays.asList(allowedMethods));
// Set allowed headers
configuration.setAllowedHeaders(Arrays.asList(allowedHeaders));
// Set exposed headers
configuration.setExposedHeaders(Arrays.asList(
"Authorization",
"X-Request-ID",
"X-Rate-Limit-Remaining"
));
// Set allow credentials
configuration.setAllowCredentials(allowCredentials);
// Set max age
configuration.setMaxAge(maxAge);
UrlBasedCorsConfigurationSource source = new UrlBasedCorsConfigurationSource();
source.registerCorsConfiguration("/api/**", configuration);
return source;
}
/**
* Development-specific security configuration
*/
@Configuration
@Profile("development")
public static class DevelopmentSecurityConfig {
@Bean
public SecurityFilterChain developmentSecurityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(authz -> authz
// Allow access to development endpoints
.requestMatchers(
"/h2-console/**",
"/actuator/**",
"/swagger-ui/**",
"/v3/api-docs/**"
).permitAll()
)
// Allow H2 console in development
.headers(headers -> headers
.frameOptions(frame -> frame.sameOrigin())
)
.csrf(csrf -> csrf
.ignoringRequestMatchers("/h2-console/**")
);
return http.build();
}
}
}
JWT Security Configuration
package com.example.secure.config;
import com.example.secure.security.JwtAuthenticationFilter;
import com.example.secure.security.JwtTokenProvider;
import com.fasterxml.jackson.databind.ObjectMapper;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.crypto.password.PasswordEncoder;
import java.security.Key;
import java.util.Base64;
import javax.crypto.spec.SecretKeySpec;
/**
* JWT Security Configuration
*/
@Configuration
public class JwtSecurityConfig {
@Value("${app.security.jwt.secret}")
private String jwtSecret;
@Value("${app.security.jwt.expiration}")
private long jwtExpiration;
@Value("${app.security.jwt.issuer}")
private String jwtIssuer;
@Value("${app.security.jwt.audience}")
private String jwtAudience;
/**
* JWT token provider
*/
@Bean
public JwtTokenProvider jwtTokenProvider(PasswordEncoder passwordEncoder, ObjectMapper objectMapper) {
Key key = new SecretKeySpec(
Base64.getDecoder().decode(jwtSecret),
"HmacSHA512"
);
return new JwtTokenProvider(
key,
jwtExpiration,
jwtIssuer,
jwtAudience,
passwordEncoder,
objectMapper
);
}
/**
* JWT authentication filter
*/
@Bean
public JwtAuthenticationFilter jwtAuthenticationFilter(JwtTokenProvider jwtTokenProvider) {
return new JwtAuthenticationFilter(jwtTokenProvider);
}
}
Encryption Configuration
package com.example.secure.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import javax.crypto.Cipher;
import javax.crypto.KeyGenerator;
import javax.crypto.SecretKey;
import javax.crypto.spec.GCMParameterSpec;
import javax.crypto.spec.SecretKeySpec;
import java.security.NoSuchAlgorithmException;
import java.security.SecureRandom;
import java.util.Base64;
/**
* Encryption configuration for data protection
*/
@Configuration
public class EncryptionConfig {
@Value("${app.security.encryption.algorithm:AES/GCM/NoPadding}")
private String encryptionAlgorithm;
@Value("${app.security.encryption.key-size:256}")
private int keySize;
@Value("${app.security.encryption.iv-size:96}")
private int ivSize;
@Value("${app.security.encryption.salt-size:128}")
private int saltSize;
@Value("${encryption.key:}")
private String encryptionKey;
@Value("${encryption.salt:}")
private String encryptionSalt;
/**
* Encryption service for sensitive data
*/
@Bean
public EncryptionService encryptionService() throws NoSuchAlgorithmException {
SecretKey secretKey;
if (encryptionKey != null && !encryptionKey.isEmpty()) {
// Use configured key
byte[] keyBytes = Base64.getDecoder().decode(encryptionKey);
secretKey = new SecretKeySpec(keyBytes, "AES");
} else {
// Generate new key (for development only)
KeyGenerator keyGenerator = KeyGenerator.getInstance("AES");
keyGenerator.init(keySize);
secretKey = keyGenerator.generateKey();
}
return new EncryptionService(
secretKey,
encryptionAlgorithm,
ivSize,
saltSize
);
}
/**
* Secure random number generator
*/
@Bean
public SecureRandom secureRandom() {
return new SecureRandom();
}
/**
* Encryption service implementation
*/
public static class EncryptionService {
private final SecretKey secretKey;
private final String algorithm;
private final int ivSize;
private final int saltSize;
private final SecureRandom secureRandom;
public EncryptionService(SecretKey secretKey, String algorithm, int ivSize, int saltSize) {
this.secretKey = secretKey;
this.algorithm = algorithm;
this.ivSize = ivSize;
this.saltSize = saltSize;
this.secureRandom = new SecureRandom();
}
public String encrypt(String plaintext) throws Exception {
byte[] iv = new byte[ivSize / 8];
secureRandom.nextBytes(iv);
Cipher cipher = Cipher.getInstance(algorithm);
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.ENCRYPT_MODE, secretKey, parameterSpec);
byte[] ciphertext = cipher.doFinal(plaintext.getBytes());
byte[] encrypted = new byte[iv.length + ciphertext.length];
System.arraycopy(iv, 0, encrypted, 0, iv.length);
System.arraycopy(ciphertext, 0, encrypted, iv.length, ciphertext.length);
return Base64.getEncoder().encodeToString(encrypted);
}
public String decrypt(String encrypted) throws Exception {
byte[] decoded = Base64.getDecoder().decode(encrypted);
byte[] iv = new byte[ivSize / 8];
System.arraycopy(decoded, 0, iv, 0, iv.length);
Cipher cipher = Cipher.getInstance(algorithm);
GCMParameterSpec parameterSpec = new GCMParameterSpec(128, iv);
cipher.init(Cipher.DECRYPT_MODE, secretKey, parameterSpec);
byte[] ciphertext = new byte[decoded.length - iv.length];
System.arraycopy(decoded, iv.length, ciphertext, 0, ciphertext.length);
byte[] plaintext = cipher.doFinal(ciphertext);
return new String(plaintext);
}
public byte[] generateSalt() {
byte[] salt = new byte[saltSize / 8];
secureRandom.nextBytes(salt);
return salt;
}
}
}
Security Headers Configuration
Custom Security Headers Filter
package com.example.secure.config;
import org.springframework.boot.web.servlet.FilterRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.core.Ordered;
import javax.servlet.*;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.util.HashMap;
import java.util.Map;
/**
* Custom security headers configuration
*/
@Configuration
public class SecurityHeadersConfig {
/**
* Custom security headers filter
*/
@Bean
public FilterRegistrationBean<SecurityHeadersFilter> securityHeadersFilter() {
FilterRegistrationBean<SecurityHeadersFilter> registrationBean =
new FilterRegistrationBean<>();
registrationBean.setFilter(new SecurityHeadersFilter());
registrationBean.addUrlPatterns("/*");
registrationBean.setOrder(Ordered.HIGHEST_PRECEDENCE);
return registrationBean;
}
/**
* Custom security headers filter implementation
*/
public static class SecurityHeadersFilter implements Filter {
private final Map<String, String> securityHeaders = new HashMap<>();
public SecurityHeadersFilter() {
// Security headers configuration
securityHeaders.put("X-Content-Type-Options", "nosniff");
securityHeaders.put("X-Frame-Options", "DENY");
securityHeaders.put("X-XSS-Protection", "1; mode=block");
securityHeaders.put("Referrer-Policy", "strict-origin-when-cross-origin");
securityHeaders.put("Permissions-Policy", "geolocation=(), microphone=(), camera=()");
securityHeaders.put("X-Permitted-Cross-Domain-Policies", "none");
securityHeaders.put("X-Download-Options", "noopen");
securityHeaders.put("X-DNS-Prefetch-Control", "off");
securityHeaders.put("Strict-Transport-Security", "max-age=31536000; includeSubDomains; preload");
// Custom headers
securityHeaders.put("X-Request-ID", "");
securityHeaders.put("X-Runtime", "");
}
@Override
public void doFilter(ServletRequest request, ServletResponse response,
FilterChain chain) throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
// Add security headers
securityHeaders.forEach((key, value) -> {
if (!httpResponse.containsHeader(key)) {
if (key.equals("X-Request-ID")) {
// Generate unique request ID
String requestId = java.util.UUID.randomUUID().toString();
httpResponse.setHeader(key, requestId);
} else if (key.equals("X-Runtime")) {
// Will be set after processing
} else {
httpResponse.setHeader(key, value);
}
}
});
// Measure processing time
long startTime = System.currentTimeMillis();
try {
chain.doFilter(request, response);
} finally {
long endTime = System.currentTimeMillis();
httpResponse.setHeader("X-Runtime", String.valueOf(endTime - startTime));
}
}
@Override
public void init(FilterConfig filterConfig) throws ServletException {
// Initialization logic if needed
}
@Override
public void destroy() {
// Cleanup logic if needed
}
}
}
Rate Limiting Configuration
package com.example.secure.config;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.data.redis.connection.RedisConnectionFactory;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.data.redis.serializer.GenericJackson2JsonRedisSerializer;
import org.springframework.data.redis.serializer.StringRedisSerializer;
import java.util.concurrent.TimeUnit;
/**
* Rate limiting configuration
*/
@Configuration
public class RateLimitingConfig {
@Value("${app.security.rate-limit.enabled:true}")
private boolean rateLimitEnabled;
@Value("${app.security.rate-limit.requests-per-minute:100}")
private int requestsPerMinute;
@Value("${app.security.rate-limit.burst-capacity:20}")
private int burstCapacity;
/**
* Rate limiting service
*/
@Bean
public RateLimitingService rateLimitingService(RedisTemplate<String, Object> redisTemplate) {
return new RateLimitingService(
redisTemplate,
rateLimitEnabled,
requestsPerMinute,
burstCapacity
);
}
/**
* Redis template for rate limiting
*/
@Bean
public RedisTemplate<String, Object> redisTemplate(RedisConnectionFactory connectionFactory) {
RedisTemplate<String, Object> template = new RedisTemplate<>();
template.setConnectionFactory(connectionFactory);
template.setKeySerializer(new StringRedisSerializer());
template.setValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setHashKeySerializer(new StringRedisSerializer());
template.setHashValueSerializer(new GenericJackson2JsonRedisSerializer());
template.setEnableTransactionSupport(true);
return template;
}
/**
* Rate limiting service implementation
*/
public static class RateLimitingService {
private final RedisTemplate<String, Object> redisTemplate;
private final boolean enabled;
private final int requestsPerMinute;
private final int burstCapacity;
public RateLimitingService(RedisTemplate<String, Object> redisTemplate,
boolean enabled, int requestsPerMinute, int burstCapacity) {
this.redisTemplate = redisTemplate;
this.enabled = enabled;
this.requestsPerMinute = requestsPerMinute;
this.burstCapacity = burstCapacity;
}
public boolean isAllowed(String clientId, String endpoint) {
if (!enabled) {
return true;
}
String key = String.format("rate_limit:%s:%s", clientId, endpoint);
long currentTime = System.currentTimeMillis();
long windowSize = TimeUnit.MINUTES.toMillis(1);
// Get current count
Long currentCount = redisTemplate.opsForValue().increment(key, 1);
if (currentCount == 1) {
// First request in this window, set expiration
redisTemplate.expire(key, windowSize, TimeUnit.MILLISECONDS);
}
return currentCount <= requestsPerMinute;
}
public RateLimitInfo getRateLimitInfo(String clientId, String endpoint) {
String key = String.format("rate_limit:%s:%s", clientId, endpoint);
Long currentCount = redisTemplate.opsForValue().increment(key, 0); // Get without incrementing
if (currentCount == null) {
currentCount = 0L;
}
long remaining = Math.max(0, requestsPerMinute - currentCount);
long resetTime = System.currentTimeMillis() + TimeUnit.MINUTES.toMillis(1);
return new RateLimitInfo(remaining, resetTime);
}
}
/**
* Rate limit information
*/
public static class RateLimitInfo {
private final long remaining;
private final long resetTime;
public RateLimitInfo(long remaining, long resetTime) {
this.remaining = remaining;
this.resetTime = resetTime;
}
public long getRemaining() { return remaining; }
public long getResetTime() { return resetTime; }
}
}
Logging Security Configuration
Secure Logging Configuration
package com.example.secure.config;
import ch.qos.logback.classic.PatternLayout;
import ch.qos.logback.classic.spi.ILoggingEvent;
import ch.qos.logback.core.pattern.PatternLayoutEncoderBase;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import java.util.regex.Pattern;
/**
* Secure logging configuration
*/
@Configuration
public class LoggingSecurityConfig {
/**
* Pattern layout with sensitive data masking
*/
@Bean
public PatternLayoutEncoderBase<ILoggingEvent> patternLayoutEncoder() {
return new SecurePatternLayoutEncoder();
}
/**
* Secure pattern layout that masks sensitive information
*/
public static class SecurePatternLayoutEncoder extends PatternLayoutEncoderBase<ILoggingEvent> {
private static final Pattern SENSITIVE_PATTERNS = Pattern.compile(
"(?i)(password|pwd|secret|key|token|auth|credential|ssn|credit.?card)=[^&,\\s]+",
Pattern.CASE_INSENSITIVE
);
@Override
public void start() {
PatternLayout patternLayout = new PatternLayout() {
@Override
public String doLayout(ILoggingEvent event) {
String original = super.doLayout(event);
return maskSensitiveData(original);
}
};
patternLayout.setContext(context);
patternLayout.setPattern(getPattern());
patternLayout.setOutputPatternAsHeader(outputPatternAsHeader);
patternLayout.start();
this.layout = patternLayout;
super.start();
}
/**
* Mask sensitive data in log messages
*/
private String maskSensitiveData(String message) {
if (message == null) {
return null;
}
// Mask passwords, tokens, etc.
String masked = SENSITIVE_PATTERNS.matcher(message)
.replaceAll("$1=***");
// Mask email addresses
masked = Pattern.compile("\\b[A-Za-z0-9._%+-]+@[A-Za-z0-9.-]+\\.[A-Z|a-z]{2,}\\b")
.matcher(masked)
.replaceAll("***@***.***");
// Mask IP addresses
masked = Pattern.compile("\\b(?:[0-9]{1,3}\\.){3}[0-9]{1,3}\\b")
.matcher(masked)
.replaceAll("***.***.***.***");
// Mask credit card numbers
masked = Pattern.compile("\\b(?:4[0-9]{12}(?:[0-9]{3})?|5[1-5][0-9]{14}|3[47][0-9]{13}|3(?:0[0-5]|[68][0-9])[0-9]{11}|6(?:011|5[0-9]{2})[0-9]{12}|(?:2131|1800|35\\d{3})\\d{11})\\b")
.matcher(masked)
.replaceAll("****************");
return masked;
}
}
}
Database Security Configuration
JPA Configuration with Security
```java
package com.example.secure.config;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.orm.jpa.EntityManagerFactoryBuilder;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.annotation.Profile;
import org.springframework.data.jpa.repository.config.EnableJpaAuditing;
import org.springframework.data.jpa.repository.config.EnableJpaRepositories;
import org.springframework.orm.jpa.JpaTransactionManager;
import org.springframework.orm.jpa.LocalContainerEntityManagerFactoryBean;
import org.springframework.transaction.PlatformTransactionManager;
import org.springframework.transaction.annotation.EnableTransactionManagement;
import javax.sql.DataSource;
import java.util.HashMap;
import java.util.Map;
/**
- JPA configuration with security enhancements
*/
@Configuration
@EnableJpaRepositories(
basePackages = "com.example.secure.repository",
entityManagerFactoryRef = "entityManagerFactory",
transactionManagerRef = "transactionManager"
)
@EnableTransactionManagement
@EnableJpaAuditing
public class DatabaseSecurityConfig { @Autowired
private DataSource dataSource; /**- Entity manager factory with security properties
*/
@Bean
public LocalContainerEntityManagerFactoryBean entityManagerFactory(
EntityManagerFactoryBuilder builder) { Map properties = new HashMap<>();
- Entity manager factory with security properties