Back to Blog
How-To 11 min read

SOC 2 Controls for Java Spring Boot: What Auditors Actually Check

Implement SOC 2-ready controls in Java Spring Boot applications — covering Spring Security configuration, secrets management, audit logging with Spring Data, and dependency scanning.

Key Takeaways
  • Configure Spring Security with method-level annotations (@PreAuthorize) and a SecurityFilterChain to enforce least-privilege access control.
  • Use Spring Vault or AWS Secrets Manager integration to inject secrets at startup — never hardcode credentials in application.properties.
  • Enable Spring Data Envers or a custom AuditingEntityListener to write immutable audit trail records to a dedicated audit table.
  • Run OWASP Dependency-Check in CI and block builds on CVSS score >= 7.
  • Use @Validated with Jakarta Bean Validation annotations on all controller method parameters and request bodies.

SOC 2 scope for Spring Boot applications

Spring Boot is the dominant Java framework for enterprise REST APIs and microservices. SOC 2 auditors examine logical access controls, change management, and monitoring — all areas where Spring has mature support.

Common audit findings in Spring Boot applications: Spring Security disabled or using permissive wildcard rules, plaintext database passwords in application.properties committed to git, no audit trail for entity mutations, and transitive dependencies with known CVEs.

This guide addresses each finding with concrete Spring configuration. All code examples target Spring Boot 3.x with Java 17+.

Spring Security configuration for CC6

Define a SecurityFilterChain bean that defaults to deny-all and explicitly permits only required endpoints. Use `http.authorizeHttpRequests(auth -> auth.requestMatchers("/api/public/**").permitAll().anyRequest().authenticated())`. Never use `.anyRequest().permitAll()` in production.

Enable method security with `@EnableMethodSecurity` and use `@PreAuthorize("hasRole('ADMIN')")` on sensitive service methods. This provides defence-in-depth: even if a route mapping changes, the method-level guard remains.

For OAuth 2.0 / JWT: configure Spring Security as a resource server with `http.oauth2ResourceServer(oauth2 -> oauth2.jwt(Customizer.withDefaults()))`. Validate the `iss`, `aud`, and `exp` claims. Store the JWT signing public key in a mounted secret, not in application.properties.

Secrets management with Spring Vault

Spring Vault (spring-cloud-vault-config) integrates with HashiCorp Vault to inject secrets as Spring properties at startup. Configure `spring.cloud.vault.token` via a Kubernetes secret or IAM role. Secrets appear as standard `@Value("${db.password}")` injections — no code change needed.

For AWS deployments, use Spring Cloud AWS with `spring.config.import=aws-secretsmanager:/myapp/prod`. Spring Cloud AWS fetches the secret JSON at startup and maps keys to Spring properties. Add `spring-cloud-aws-secrets-manager` to your pom.xml.

Add a Gitleaks or trufflehog pre-commit hook to scan for hardcoded credentials. Document the scanning step in your change management procedure (CC8.1). Run `git log --all --full-history -- "*.properties"` periodically to ensure no historical commits contain credentials.

Audit logging with Spring Data Envers

Spring Data Envers (hibernate-envers) automatically writes a revision record to a `*_AUD` table on every INSERT, UPDATE, or DELETE of annotated entities. Add `@Audited` to any JPA entity you want tracked. Envers records the revision number, timestamp, operation type, and the full field snapshot.

For authentication events (login, logout, failed login), implement an `ApplicationListener<AbstractAuthenticationEvent>` and write records to a dedicated `audit_events` table with fields: event_type, user_id, ip_address, timestamp, outcome.

Protect audit tables with a separate DB role that has INSERT-only permissions. Application code should never UPDATE or DELETE audit records. Grant SELECT to the auditor portal service account only. This satisfies CC7.2 and provides an immutable trail for auditor review.

Dependency scanning with OWASP Dependency-Check

Add the `dependency-check-maven` plugin to your pom.xml. Configure `<failBuildOnCVSS>7</failBuildOnCVSS>` to block builds when any dependency has a CVSS score of 7 or higher. The plugin checks against the NVD database.

Run the check in your CI pipeline (GitHub Actions: `mvn verify -Powasp`). Cache the NVD database between runs to avoid rate limiting. Publish the HTML report as a CI artifact for auditor review.

Maintain a suppression file (`dependency-check-suppressions.xml`) for accepted false positives. Each suppression must include a `<notes>` element explaining the risk acceptance rationale and an expiry date. Review suppressions quarterly as part of your vulnerability management programme.

Input validation with Bean Validation

Annotate all request body DTOs with Jakarta Bean Validation constraints: `@NotBlank`, `@Size(max=255)`, `@Email`, `@Pattern`. Add `@Validated` to your `@RestController` class and `@Valid` to `@RequestBody` parameters. Spring will automatically return a 400 with constraint violation details.

For custom business rules, implement `ConstraintValidator<T, V>` and create a custom annotation. This keeps validation logic co-located with the DTO and testable in isolation.

Note on Jackson: if you have a boolean field `isActive` on a DTO, add `@JsonProperty("isActive")` to ensure Jackson serialises the field name correctly. Jackson by default uses the getter name for booleans (`isActive` getter → `"active"` JSON key), which can cause frontend type mismatches and is a known source of subtle bugs.

Collecting evidence for auditors

Auditors typically request: (1) SecurityFilterChain configuration showing deny-all default; (2) A sample Envers audit trail showing entity history; (3) OWASP Dependency-Check report from the most recent production deployment build; (4) Secret scanning CI step passing with zero findings; (5) TLS configuration showing minimum TLS 1.2.

For Spring Boot, you can expose a custom `/actuator/security-config` endpoint (protected, ADMIN role only) that returns the active security policy as JSON. This gives auditors a machine-readable snapshot without needing a codebase walkthrough.

Map your evidence to Trust Service Criteria: SecurityFilterChain → CC6.2 (logical access), Envers → CC7.2 (monitoring), Dependency-Check → CC7.1 (vulnerability management), Bean Validation → CC6.6 (input handling), Secrets Manager → CC6.1 (credential storage).

Frequently Asked Questions

Does Spring Boot auto-configuration create any SOC 2 risks?
Auto-configuration is convenient but can enable features you do not need, such as H2 console in production or Spring Boot Actuator endpoints without authentication. Always set `spring.h2.console.enabled=false` and secure all Actuator endpoints behind a role check in your SecurityFilterChain. Review the auto-configuration report with --debug on startup.
What is the difference between Spring Security RBAC and ABAC?
RBAC (role-based) uses @PreAuthorize("hasRole('ADMIN')") — simple and auditor-friendly. ABAC (attribute-based) uses @PreAuthorize expressions that check resource ownership or contextual attributes. For SOC 2, either is acceptable; what matters is that access rules are documented and testable. Start with RBAC and layer ABAC where fine-grained control is needed.
Does Hibernate parameterise all queries automatically?
Yes — JPQL and Criteria API queries are always parameterised by Hibernate. SQL injection risk only appears with EntityManager.createNativeQuery() using string concatenation. Use named parameters (:paramName) or positional parameters (?1) in native queries.
How do I handle secrets in integration tests without Vault?
Use Testcontainers with the Vault container for integration tests, or use Spring profiles with a test-only application-test.properties that reads from environment variables set by your CI runner. Never commit real credentials to test configuration files.
Does running on AWS ECS satisfy any SOC 2 infrastructure controls?
Running on ECS (Fargate) means AWS is responsible for the host OS and hypervisor layer — those controls are covered by the AWS SOC 2 report. Your responsibility covers container configuration, IAM task roles, security groups, and application-level controls. Maintain an AWS responsibility matrix in your system description.

Automate your compliance today

AuditPath runs 86+ automated checks across AWS, GitHub, Okta, and 14 more integrations. SOC 2 and DPDP Act. Free plan available.

Start for free