Building Fortified Applications: A Comprehensive Guide to Java Configuration Hardening


Article

In Java application security, the focus often falls on code-level vulnerabilities while configuration weaknesses remain a prevalent attack vector. Configuration Hardening is the process of securing an application's settings, dependencies, and runtime environment to minimize the attack surface. For Java applications, this involves systematically addressing security gaps in frameworks, servers, dependencies, and JVM settings to create a robust, defense-in-depth security posture.

What is Java Configuration Hardening?

Configuration hardening involves securing every configurable aspect of your Java application stack:

  • JVM Security Settings: Memory, execution, and security manager configurations
  • Framework Security: Spring Security, Jakarta EE, and microframework settings
  • Server Configuration: Tomcat, Jetty, Undertow, and application server security
  • Dependency Management: Secure dependency versions and vulnerability management
  • Runtime Environment: Container, OS, and cloud platform security settings

JVM Security Hardening

1. Secure JVM Launch Parameters

Configure JVM for security-first operation:

# Secure JVM launch configuration
java \
# Memory and execution settings
-Xmx1024m -Xms512m \
-XX:MaxMetaspaceSize=256m \
-XX:ReservedCodeCacheSize=128m \
# Security manager and policy (if needed)
-Djava.security.manager \
-Djava.security.policy==/app/conf/security.policy \
# Cryptographic strength configuration
-Djava.security.properties=/app/conf/java.security \
-Djdk.tls.disabledAlgorithms=SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA \
-Djdk.certpath.disabledAlgorithms=MD2, MD5, SHA1 jdkCA & usage TLSServer \
# RMI and serialization security
-Dsun.rmi.dgc.server.gcInterval=3600000 \
-Dsun.rmi.dgc.client.gcInterval=3600000 \
-Djava.rmi.server.hostname=localhost \
# Logging and debugging security
-Djava.awt.headless=true \
-Dlog4j2.formatMsgNoLookups=true \
# Application-specific settings
-Dfile.encoding=UTF-8 \
-Duser.timezone=UTC \
-jar application.jar

2. Java Security Policy File

Create a restrictive security policy:

// security.policy
grant {
// Basic permissions for all code
permission java.util.PropertyPermission "user.timezone", "read";
permission java.util.PropertyPermission "file.encoding", "read";
permission java.util.PropertyPermission "java.io.tmpdir", "read";
// Application-specific permissions
permission java.io.FilePermission "/app/logs/-", "read,write";
permission java.net.SocketPermission "database.company.com:5432", "connect";
permission java.net.SocketPermission "redis.company.com:6379", "connect";
// Restrict dangerous permissions
permission java.lang.RuntimePermission "setFactory", "";
permission java.lang.RuntimePermission "setIO", "";
permission java.lang.Reflect.ReflectPermission "suppressAccessChecks", "";
// Deny critical permissions
permission java.io.FilePermission "<<ALL FILES>>", "read,write,execute,delete", deny;
permission java.net.SocketPermission "*:0-1023", "connect,accept,listen", deny;
permission java.lang.RuntimePermission "exitVM", "", deny;
};

Spring Framework Hardening

1. Spring Security Configuration

Implement comprehensive security controls:

@Configuration
@EnableWebSecurity
public class SecurityHardeningConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
return http
// Disable dangerous features
.csrf(csrf -> csrf
.ignoringRequestMatchers("/api/public/**")
)
.headers(headers -> headers
.contentSecurityPolicy(csp -> csp
.policyDirectives("default-src 'self'; script-src 'self' 'unsafe-inline'"))
.httpStrictTransportSecurity(hsts -> hsts
.includeSubDomains(true)
.maxAgeInSeconds(31536000))
.frameOptions(frame -> frame
.deny())
.xssProtection(xss -> xss
.headerValue(XXssConfig.HeaderValue.ENABLED_MODE_BLOCK))
)
// Session management
.sessionManagement(session -> session
.sessionCreationPolicy(SessionCreationPolicy.IF_REQUIRED)
.sessionFixation(sf -> sf
.migrateSession())
.maximumSessions(1)
)
// Authorization
.authorizeHttpRequests(authz -> authz
.requestMatchers("/actuator/**").hasRole("ADMIN")
.requestMatchers("/api/admin/**").hasRole("ADMIN")
.requestMatchers("/api/user/**").hasRole("USER")
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
// Authentication
.formLogin(form -> form
.loginPage("/login")
.failureHandler(authenticationFailureHandler())
)
.logout(logout -> logout
.deleteCookies("JSESSIONID")
.invalidateHttpSession(true)
)
.build();
}
@Bean
public AuthenticationFailureHandler authenticationFailureHandler() {
return new CustomAuthenticationFailureHandler();
}
}

2. Spring Boot Application Properties Hardening

Secure application configuration:

# application-security.properties
# Server security
server.servlet.session.timeout=1800
server.servlet.session.cookie.http-only=true
server.servlet.session.cookie.secure=true
server.servlet.session.cookie.same-site=strict
server.port=8443
server.ssl.enabled=true
server.ssl.key-store=classpath:keystore.p12
server.ssl.key-store-password=${KEYSTORE_PASSWORD}
server.ssl.key-store-type=PKCS12
server.ssl.key-alias=tomcat
# Spring security
spring.security.oauth2.client.registration.provider.require-https=true
spring.security.filter.order=0
# Database security
spring.datasource.hikari.connection-timeout=30000
spring.datasource.hikari.maximum-pool-size=20
spring.datasource.hikari.validation-timeout=5000
spring.datasource.hikari.leak-detection-threshold=60000
# Actuator security
management.endpoints.web.exposure.include=health,info,metrics
management.endpoint.health.show-details=when_authorized
management.endpoints.web.base-path=/internal/actuator
management.server.port=9090
# Logging security
logging.pattern.console=%d{yyyy-MM-dd HH:mm:ss} - %logger{36} - %msg%n
logging.level.com.company.sensitive=OFF
# Jackson security
spring.jackson.default-property-inclusion=non_null
spring.jackson.deserialization.fail-on-unknown-properties=true
# File upload security
spring.servlet.multipart.max-file-size=10MB
spring.servlet.multipart.max-request-size=10MB

Tomcat Server Hardening

1. Embedded Tomcat Configuration

Harden embedded Tomcat in Spring Boot:

@Configuration
public class TomcatHardeningConfig {
@Bean
public TomcatServletWebServerFactory servletContainer() {
TomcatServletWebServerFactory tomcat = new TomcatServletWebServerFactory();
tomcat.addConnectorCustomizers(connector -> {
// HTTP to HTTPS redirect
connector.setRedirectPort(8443);
connector.setScheme("https");
connector.setSecure(true);
// Connection hardening
connector.setAttribute("maxPostSize", 10485760); // 10MB
connector.setAttribute("maxSavePostSize", 4096);
connector.setAttribute("allowTrace", false);
});
// Add security valve
tomcat.addContextValves(securityValve());
// Error page configuration
tomcat.addErrorPages(new ErrorPage(HttpStatus.NOT_FOUND, "/error/404"));
tomcat.addErrorPages(new ErrorPage(HttpStatus.INTERNAL_SERVER_ERROR, "/error/500"));
return tomcat;
}
private Valve securityValve() {
return new RemoteAddrValve() {{
setAllow("127.0.0.1,::1,10.0.0.0/8");
setDenyStatus(404);
}};
}
}

2. server.xml Hardening (External Tomcat)

<!-- server.xml -->
<Server port="8005" shutdown="SHUTDOWN">
<Listener className="org.apache.catalina.security.SecurityListener" />
<Service name="Catalina">
<Connector port="8443" protocol="HTTP/1.1"
SSLEnabled="true"
maxThreads="150"
minSpareThreads="25"
maxHttpHeaderSize="8192"
enableLookups="false"
disableUploadTimeout="true"
acceptCount="100"
maxPostSize="10485760"
maxSavePostSize="4096"
allowTrace="false"
server="Unknown"
xpoweredBy="false">
<SSLHostConfig certificateVerification="false"
protocols="TLSv1.2,TLSv1.3"
ciphers="TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256,...">
</SSLHostConfig>
</Connector>
<Engine name="Catalina" defaultHost="localhost">
<Host name="localhost" appBase="webapps"
unpackWARs="true" autoDeploy="false">
<Valve className="org.apache.catalina.valves.RemoteAddrValve"
allow="127\.0\.0\.1,::1,10\.\d+\.\d+\.\d+" />
<Valve className="org.apache.catalina.valves.ErrorReportValve"
showReport="false" showServerInfo="false" />
</Host>
</Engine>
</Service>
</Server>

Dependency Security Hardening

1. Maven Security Configuration

Secure dependency management:

<!-- pom.xml -->
<project>
<properties>
<!-- Enforce secure dependency versions -->
<log4j2.version>2.17.1</log4j2.version>
<jackson.version>2.13.4.2</jackson.version>
<spring-boot.version>2.7.8</spring-boot.version>
<!-- Security plugin versions -->
<owasp-dependency-check.version>7.4.4</owasp-dependency-check.version>
<spotbugs.version>4.7.3</spotbugs.version>
</properties>
<dependencyManagement>
<dependencies>
<!-- BOM imports for vulnerability management -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-dependencies</artifactId>
<version>${spring-boot.version}</version>
<type>pom</type>
<scope>import</scope>
</dependency>
</dependencies>
</dependencyManagement>
<build>
<plugins>
<!-- OWASP Dependency Check -->
<plugin>
<groupId>org.owasp</groupId>
<artifactId>dependency-check-maven</artifactId>
<version>${owasp-dependency-check.version}</version>
<executions>
<execution>
<goals>
<goal>check</goal>
</goals>
</execution>
</executions>
<configuration>
<failBuildOnAnyVulnerability>true</failBuildOnAnyVulnerability>
<suppressionFile>dependency-check-suppressions.xml</suppressionFile>
</configuration>
</plugin>
<!-- SpotBugs Security -->
<plugin>
<groupId>com.github.spotbugs</groupId>
<artifactId>spotbugs-maven-plugin</artifactId>
<version>${spotbugs.version}</version>
<configuration>
<effort>Max</effort>
<threshold>Low</threshold>
<includeFilterFile>spotbugs-security-include.xml</includeFilterFile>
</configuration>
</plugin>
</plugins>
</build>
</project>

2. Dependency Exclusion Policy

<!-- Ban vulnerable dependencies -->
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-enforcer-plugin</artifactId>
<version>3.1.0</version>
<executions>
<execution>
<id>ban-vulnerable-dependencies</id>
<goals>
<goal>enforce</goal>
</goals>
<configuration>
<rules>
<bannedDependencies>
<excludes>
<exclude>log4j:log4j:(,1.2.17]</exclude>
<exclude>commons-collections:commons-collections:(,3.2.2]</exclude>
<exclude>com.fasterxml.jackson.core:jackson-databind:[2.0.0,2.12.6.1)</exclude>
</excludes>
</bannedDependencies>
<requireJavaVersion>
<version>[17,18)</version>
</requireJavaVersion>
</rules>
</configuration>
</execution>
</executions>
</plugin>

Logging Security Hardening

1. Secure Logback Configuration

<!-- logback-spring.xml -->
<configuration>
<!-- Disable sensitive data logging -->
<conversionRule conversionWord="maskedMsg" converterClass="com.company.security.MaskingPatternLayout" />
<appender name="SECURE_FILE" class="ch.qos.logback.core.rolling.RollingFileAppender">
<file>logs/application.log</file>
<rollingPolicy class="ch.qos.logback.core.rolling.TimeBasedRollingPolicy">
<fileNamePattern>logs/application.%d{yyyy-MM-dd}.%i.log.gz</fileNamePattern>
<maxHistory>30</maxHistory>
<totalSizeCap>3GB</totalSizeCap>
</rollingPolicy>
<encoder>
<pattern>%d{yyyy-MM-dd HH:mm:ss} [%thread] %-5level %logger{36} - %maskedMsg%n</pattern>
</encoder>
</appender>
<!-- Prevent Log4Shell-type vulnerabilities -->
<logger name="org.apache.logging.log4j" level="WARN"/>
<!-- Sensitive package logging -->
<logger name="com.company.security" level="WARN"/>
<logger name="com.company.user" level="INFO"/>
<root level="INFO">
<appender-ref ref="SECURE_FILE" />
</root>
</configuration>

2. Log Masking Implementation

@Component
public class MaskingPatternLayout extends MessageConverter {
private static final List<Pattern> SENSITIVE_PATTERNS = Arrays.asList(
Pattern.compile("(\"password\"\\s*:\\s*\")([^\"]+)(\")", Pattern.CASE_INSENSITIVE),
Pattern.compile("(password=)([^&]+)", Pattern.CASE_INSENSITIVE),
Pattern.compile("(\\b\\d{16}\\b)"), // Credit card numbers
Pattern.compile("(\\b[\\w.%-]+@[\\w.-]+\\.[A-Z]{2,4}\\b)", Pattern.CASE_INSENSITIVE) // Email
);
@Override
public String convert(ILoggingEvent event) {
String message = event.getFormattedMessage();
return maskSensitiveData(message);
}
private String maskSensitiveData(String message) {
String masked = message;
for (Pattern pattern : SENSITIVE_PATTERNS) {
masked = pattern.matcher(masked).replaceAll("$1***$3");
}
return masked;
}
}

Container Security Hardening

1. Dockerfile Security

# Use minimal base image
FROM eclipse-temurin:17-jre-alpine
# Add security scanning
USER root
RUN apk add --no-cache clamav && freshclam
# Create non-root user
RUN addgroup -S appgroup && adduser -S appuser -G appgroup
# Security configuration
COPY security.policy /app/conf/security.policy
COPY java.security /app/conf/java.security
# Copy application
COPY target/application.jar /app/application.jar
COPY entrypoint.sh /app/entrypoint.sh
# Set secure permissions
RUN chown -R appuser:appgroup /app && \
chmod -R 750 /app && \
chmod 400 /app/conf/*.policy && \
chmod 500 /app/entrypoint.sh
# Security labels
LABEL security.scan.enabled="true" \
security.policy.version="1.0" \
maintainer="[email protected]"
# Switch to non-root user
USER appuser
# Secure entrypoint
ENTRYPOINT ["/app/entrypoint.sh"]
# Health check
HEALTHCHECK --interval=30s --timeout=3s --start-period=5s --retries=3 \
CMD curl -f http://localhost:8080/actuator/health || exit 1

2. Secure Entrypoint Script

#!/bin/sh
# entrypoint.sh
set -e
# Validate environment variables
if [ -z "$KEYSTORE_PASSWORD" ]; then
echo "KEYSTORE_PASSWORD environment variable is required"
exit 1
fi
# Set secure umask
umask 0027
# Run application with security settings
exec java \
-Djava.security.manager \
-Djava.security.policy==/app/conf/security.policy \
-Djava.security.properties==/app/conf/java.security \
-Djdk.tls.disabledAlgorithms="SSLv3, TLSv1, TLSv1.1, RC4, DES, MD5withRSA" \
-Dlog4j2.formatMsgNoLookups=true \
-jar /app/application.jar

Runtime Security Monitoring

1. Security Event Monitoring

@Component
public class SecurityEventMonitor {
private static final Logger securityLogger = LoggerFactory.getLogger("SECURITY");
@EventListener
public void handleAuthenticationEvent(AbstractAuthenticationEvent event) {
if (event instanceof AuthenticationSuccessEvent) {
securityLogger.info("Authentication success: {}", 
event.getAuthentication().getName());
} else if (event instanceof AuthenticationFailureBadCredentialsEvent) {
securityLogger.warn("Authentication failure: {}", 
((AuthenticationFailureBadCredentialsEvent) event).getException().getMessage());
}
}
@EventListener 
public void handleAuthorizationEvent(AuthorizationEvent event) {
if (!event.isGranted()) {
securityLogger.warn("Authorization denied: {} for {}", 
event.getAuthentication().getName(),
event.getSource());
}
}
}

2. Configuration Validation

@Component
public class SecurityConfigurationValidator {
@EventListener
public void validateConfiguration(ApplicationReadyEvent event) {
checkTlsConfiguration();
checkPasswordStrength();
checkSessionSecurity();
}
private void checkTlsConfiguration() {
String disabledAlgorithms = Security.getProperty("jdk.tls.disabledAlgorithms");
if (!disabledAlgorithms.contains("TLSv1") || !disabledAlgorithms.contains("TLSv1.1")) {
throw new SecurityConfigurationException("Insecure TLS protocols are enabled");
}
}
private void checkPasswordStrength() {
String password = environment.getProperty("spring.datasource.password");
if (password != null && password.length() < 12) {
throw new SecurityConfigurationException("Database password is too weak");
}
}
}

Best Practices for Java Configuration Hardening

  1. Principle of Least Privilege: Run applications with minimal required permissions
  2. Defense in Depth: Implement multiple layers of security controls
  3. Secure Defaults: Configure security settings explicitly rather than relying on defaults
  4. Continuous Validation: Regularly audit and validate security configurations
  5. Secret Management: Never store secrets in configuration files; use secure secret management
  6. Automated Security Testing: Integrate security checks into CI/CD pipelines
  7. Regular Updates: Keep dependencies and frameworks updated with security patches

Security Configuration Checklist

  • [ ] JVM security manager enabled
  • [ ] TLS 1.0/1.1 disabled
  • [ ] Secure cryptographic algorithms configured
  • [ ] Non-root user for application execution
  • [ ] Session timeout configured
  • [ ] CSRF protection enabled
  • [ ] Security headers configured
  • [ ] Dependency vulnerability scanning implemented
  • [ ] Secure logging configured
  • [ ] Error messages sanitized
  • [ ] File upload restrictions configured

Conclusion

Java configuration hardening is a critical aspect of application security that addresses vulnerabilities beyond code-level issues. By systematically securing JVM settings, framework configurations, server setups, and runtime environments, organizations can significantly reduce their attack surface and build resilient applications.

The comprehensive approach to configuration hardening—covering everything from JVM security policies to container runtime settings—ensures that Java applications are protected against common configuration-based attacks. In today's threat landscape, where attackers increasingly target configuration weaknesses, implementing robust hardening practices is not just a security best practice but a business imperative for any organization running Java applications in production.

Advanced Java Supply Chain Security, Kubernetes Hardening & Runtime Threat Detection

Sigstore Rekor in Java – https://macronepal.com/blog/sigstore-rekor-in-java/
Explains integrating Sigstore Rekor into Java systems to create a transparent, tamper-proof log of software signatures and metadata for verifying supply chain integrity.

Securing Java Applications with Chainguard Wolfi – https://macronepal.com/blog/securing-java-applications-with-chainguard-wolfi-a-comprehensive-guide/
Explains using Chainguard Wolfi minimal container images to reduce vulnerabilities and secure Java applications with hardened, lightweight runtime environments.

Cosign Image Signing in Java Complete Guide – https://macronepal.com/blog/cosign-image-signing-in-java-complete-guide/
Explains how to digitally sign container images using Cosign in Java-based workflows to ensure authenticity and prevent unauthorized modifications.

Secure Supply Chain Enforcement Kyverno Image Verification for Java Containers – https://macronepal.com/blog/secure-supply-chain-enforcement-kyverno-image-verification-for-java-containers/
Explains enforcing Kubernetes policies with Kyverno to verify container image signatures and ensure only trusted Java container images are deployed.

Pod Security Admission in Java Securing Kubernetes Deployments for JVM Applications – https://macronepal.com/blog/pod-security-admission-in-java-securing-kubernetes-deployments-for-jvm-applications/
Explains Kubernetes Pod Security Admission policies that enforce security rules like restricted privileges and safe configurations for Java workloads.

Securing Java Applications at Runtime Kubernetes Security Context – https://macronepal.com/blog/securing-java-applications-at-runtime-a-guide-to-kubernetes-security-context/
Explains how Kubernetes security contexts control runtime permissions, user IDs, and access rights for Java containers to improve isolation.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring-2/
Explains detecting abnormal runtime behavior in Java applications to identify potential security threats using process monitoring techniques.

Achieving Security Excellence CIS Benchmark Compliance for Java Applications – https://macronepal.com/blog/achieving-security-excellence-implementing-cis-benchmark-compliance-for-java-applications/
Explains applying CIS security benchmarks to Java environments to standardize hardening and improve overall system security posture.

Process Anomaly Detection in Java Behavioral Monitoring – https://macronepal.com/blog/process-anomaly-detection-in-java-comprehensive-behavioral-monitoring/
Explains behavioral monitoring of Java processes to detect anomalies and improve runtime security through continuous observation and analysis.

Leave a Reply

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


Macro Nepal Helper