Spring Boot Deployment Security Guide: From Monolith to Cloud‑Native Zero‑Trust Production

This comprehensive guide walks through why simple Spring Security is insufficient for production, outlines eight core security principles, presents a threat model, and provides step‑by‑step recommendations—including authentication, token design, configuration management, input validation, logging, rate limiting, container hardening, Kubernetes policies, service‑mesh mTLS, supply‑chain scanning, and audit—to transform a Spring Boot application into a zero‑trust, production‑grade service.

Cloud Architecture
Cloud Architecture
Cloud Architecture
Spring Boot Deployment Security Guide: From Monolith to Cloud‑Native Zero‑Trust Production

Why Spring Boot deployment security goes beyond adding Spring Security

Adding JWT, BCrypt or endpoint permissions is only a part of the picture. Production‑grade security requires a complete trust chain covering code provenance, configuration secrecy, image hygiene, minimal‑privilege deployment, request filtering, service‑to‑service protection, data leakage prevention and rapid incident response.

Eight core principles for a secure deployment

Least‑privilege – run containers as non‑root, isolate DB accounts, restrict admin interfaces.

Default‑deny – explicit allow rules for network, gateway and WAF.

Zero‑trust – enforce mTLS, signed tokens, MFA for operations.

Defense‑in‑depth – protect at boundary, gateway, application, runtime and data layers.

Auditability – record who accessed what, when and from where.

Rollbackability – configuration gray‑release, versioned policies, dynamic switches.

High‑concurrency stability – layered rate limiting, thread‑pool isolation, circuit breaking.

Supply‑chain trust – signed images, SBOM, vulnerability scanning.

Threat model and common high‑risk issues

The typical attack surface includes public entry, gateway, APIs, management endpoints, config stores, images, DB/Redis/MQ, logging and CI/CD. Concrete consequences are actuator exposure, root containers, long‑lived symmetric keys, hard‑coded passwords and insufficient logging.

Production‑grade architecture

A layered diagram (Internet → CDN/WAF → Ingress → Gateway → Spring Boot filter chain → business logic → DB/Redis → observability) shows where security controls belong.

Authentication & Authorization design

Instead of hand‑rolled JWT providers, use standard OAuth2/OIDC with Spring Security 6 Resource Server, asymmetric RSA/ECDSA keys, short‑lived access tokens (5‑15 min), refresh tokens (7‑30 days) and token revocation via Redis blacklists.

package com.example.security;

import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpMethod;
import org.springframework.security.config.Customizer;
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.configurers.AbstractHttpConfigurer;
import org.springframework.security.web.SecurityFilterChain;

@Configuration
@EnableMethodSecurity
public class SecurityConfig {
    @Bean
    public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
        http.csrf(AbstractHttpConfigurer::disable)
            .cors(Customizer.withDefaults())
            .sessionManagement(session -> session.disable())
            .headers(headers -> headers
                .contentSecurityPolicy(csp -> csp.policyDirectives("default-src 'self'"))
                .frameOptions(frame -> frame.deny())
                .xssProtection(Customizer.withDefaults())
                .httpStrictTransportSecurity(hsts -> hsts
                    .includeSubDomains(true)
                    .maxAgeInSeconds(31536000)))
            .authorizeHttpRequests(auth -> auth
                .requestMatchers("/actuator/health", "/actuator/prometheus").permitAll()
                .requestMatchers("/actuator/**").hasRole("OPS")
                .requestMatchers(HttpMethod.POST, "/api/auth/logout").authenticated()
                .requestMatchers("/api/admin/**").hasRole("ADMIN")
                .requestMatchers(HttpMethod.GET, "/api/orders/**").hasAnyRole("USER", "ADMIN")
                .anyRequest().authenticated())
            .oauth2ResourceServer(oauth2 -> oauth2.jwt(jwt -> jwt.jwtAuthenticationConverter(jwtAuthenticationConverter())));
        return http.build();
    }
    // jwtAuthenticationConverter implementation omitted for brevity
}

Token recommendations

Access Token TTL: 5‑15 minutes

Refresh Token TTL: 7‑30 days (stateful on server)

Signature algorithm: RS256 or ES256

Mandatory claims: iss, sub, aud, exp, iat, jti

Risk‑control claims: device_id, tenant_id, client_id, scope

Revocation: Redis blacklist or version‑based mechanism

Key rotation: support at least dual‑key coexistence

Configuration & secret management

Avoid hard‑coding passwords in application-prod.yml. Use Vault, KMS or cloud Secrets Manager and inject secrets at runtime via environment variables or mounted secrets, never logging them.

spring:
  datasource:
    url: jdbc:mysql://prod-db:3306/order
    username: ${DB_USER}
    password: ${DB_PASSWORD}

Input validation & output sanitization

All request DTOs should use Bean Validation, enforce pagination limits and reject unsafe content types. Sensitive fields (phone, ID, bank card) must be masked before logging or returning.

public record QueryOrderRequest(
    @NotBlank @Pattern(regexp = "^[A-Z0-9_-]{8,32}$") String orderNo,
    @Min(1) @Max(100) int pageSize) {}

Logging governance

Separate audit logs from business logs, mask PII and include traceId, userId, tenantId, IP, endpoint, resourceId, outcome, latency and risk tags.

{
  "timestamp":"2026-04-10T11:20:31.210+08:00",
  "eventType":"ORDER_QUERY",
  "traceId":"2f2d754c7f3341f8",
  "userId":"100245",
  "tenantId":"mall-cn",
  "resourceId":"89001231",
  "clientIp":"10.20.18.2",
  "outcome":"SUCCESS",
  "latencyMs":32
}

Rate limiting, isolation and idempotency

Application‑level limits are insufficient; combine CDN/WAF, gateway Redis token bucket and local fallback.

package com.example.common.ratelimit;

import io.github.bucket4j.Bandwidth;
import io.github.bucket4j.Bucket;
import io.github.bucket4j.Refill;
import java.time.Duration;
import java.util.Map;
import java.util.concurrent.ConcurrentHashMap;
import org.springframework.stereotype.Component;

@Component
public class LocalRateLimiter {
    private final Map<String, Bucket> buckets = new ConcurrentHashMap<>();
    public boolean tryConsume(String key, long capacity, long refillTokens, Duration period) {
        Bucket bucket = buckets.computeIfAbsent(key, k -> Bucket.builder()
            .addLimit(Bandwidth.classic(capacity, Refill.intervally(refillTokens, period)))
            .build());
        return bucket.tryConsume(1);
    }
}
package com.example.common.ratelimit;

import jakarta.servlet.FilterChain;
import jakarta.servlet.ServletException;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.time.Duration;
import lombok.RequiredArgsConstructor;
import org.springframework.http.HttpStatus;
import org.springframework.stereotype.Component;
import org.springframework.web.filter.OncePerRequestFilter;

@Component
@RequiredArgsConstructor
public class LoginRateLimitFilter extends OncePerRequestFilter {
    private final LocalRateLimiter rateLimiter;
    @Override
    protected boolean shouldNotFilter(HttpServletRequest request) {
        return !"/api/auth/login".equals(request.getRequestURI());
    }
    @Override
    protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
            throws ServletException, IOException {
        String key = request.getRemoteAddr();
        boolean allowed = rateLimiter.tryConsume(key, 20, 20, Duration.ofMinutes(1));
        if (!allowed) {
            response.setStatus(HttpStatus.TOO_MANY_REQUESTS.value());
            response.getWriter().write("{\"code\":\"RATE_LIMITED\",\"message\":\"请求过于频繁\"}");
            return;
        }
        filterChain.doFilter(request, response);
    }
}

Thread‑pool & connection‑pool segregation

Separate pools for authentication, order processing, file export and external calls to avoid resource contention during spikes.

Container security

A multi‑stage Dockerfile builds a minimal JRE image, creates a non‑root user, sets a read‑only root filesystem, disables privileged mode and adds health checks.

FROM maven:3.9.0-eclipse-temurin-21 AS builder
WORKDIR /workspace
COPY pom.xml .
COPY src ./src
RUN mvn -B -DskipTests clean package

FROM eclipse-temurin:21-jre-jammy
RUN groupadd --system spring && useradd --system --gid spring --create-home spring
WORKDIR /app
COPY --from=builder /workspace/target/*.jar app.jar
ENV JAVA_TOOL_OPTIONS="-XX:+UseContainerSupport -XX:MaxRAMPercentage=70 -XX:InitialRAMPercentage=30 -XX:+ExitOnOutOfMemoryError -Djava.security.egd=file:/dev/./urandom -Dfile.encoding=UTF-8"
RUN chown -R spring:spring /app
USER spring:spring
EXPOSE 8080
HEALTHCHECK --interval=30s --timeout=5s --start-period=40s --retries=3 \
  CMD wget -qO- http://127.0.0.1:8080/actuator/health/readiness || exit 1
ENTRYPOINT ["java","-jar","/app/app.jar"]

Kubernetes deployment hardening

Deployment adds a securityContext (runAsNonRoot, dropAll capabilities, readOnlyRootFilesystem), disables automatic ServiceAccount token mounting, and defines a NetworkPolicy that only allows traffic from the ingress namespace and restricts egress to the database and Redis.

apiVersion: apps/v1
kind: Deployment
metadata:
  name: order-service
  namespace: prod-order
spec:
  replicas: 3
  selector:
    matchLabels:
      app: order-service
  template:
    metadata:
      labels:
        app: order-service
    spec:
      serviceAccountName: order-service
      automountServiceAccountToken: false
      securityContext:
        runAsNonRoot: true
        runAsUser: 10001
        readOnlyRootFilesystem: true
        capabilities:
          drop: ["ALL"]
      containers:
      - name: order-service
        image: registry.example.com/order-service:1.4.2
        ports:
        - containerPort: 8080
        env:
        - name: SPRING_PROFILES_ACTIVE
          value: prod
        envFrom:
        - secretRef:
            name: order-service-secret
        resources:
          requests:
            cpu: "500m"
            memory: "768Mi"
          limits:
            cpu: "2"
            memory: "2Gi"
        livenessProbe:
          httpGet:
            path: /actuator/health/liveness
            port: 8080
          initialDelaySeconds: 40
          periodSeconds: 15
        readinessProbe:
          httpGet:
            path: /actuator/health/readiness
            port: 8080
          initialDelaySeconds: 20
          periodSeconds: 10
        volumeMounts:
        - name: tmp
          mountPath: /tmp
      volumes:
      - name: tmp
        emptyDir: {}
apiVersion: networking.k8s.io/v1
kind: NetworkPolicy
metadata:
  name: order-service-policy
  namespace: prod-order
spec:
  podSelector:
    matchLabels:
      app: order-service
  policyTypes:
  - Ingress
  - Egress
  ingress:
  - from:
    - namespaceSelector:
        matchLabels:
          name: ingress-system
    - podSelector:
        matchLabels:
          app: gateway
    ports:
    - protocol: TCP
      port: 8080
  egress:
  - to:
    - namespaceSelector:
        matchLabels:
          name: prod-data
    ports:
    - protocol: TCP
      port: 3306
    - protocol: TCP
      port: 6379

Service‑mesh mTLS example (Istio)

apiVersion: security.istio.io/v1beta1
kind: PeerAuthentication
metadata:
  name: default
  namespace: prod-order
spec:
  mtls:
    mode: STRICT
---
apiVersion: security.istio.io/v1beta1
kind: AuthorizationPolicy
metadata:
  name: order-service-allow-gateway
  namespace: prod-order
spec:
  selector:
    matchLabels:
      app: order-service
  rules:
  - from:
    - source:
        principals: ["cluster.local/ns/ingress-system/sa/gateway"]

Actuator hardening

Expose Actuator on a separate internal port, limit exposure to health and Prometheus endpoints, and protect the rest with ROLE_OPS.

management:
  server:
    port: 18080
  endpoints:
    web:
      exposure:
        include: health,prometheus
      base-path: /actuator
  endpoint:
    health:
      probes:
        enabled: true
      show-details: never
.authorizeHttpRequests(auth -> auth
    .requestMatchers("/actuator/health", "/actuator/prometheus").permitAll()
    .requestMatchers("/actuator/**").hasRole("OPS"))

Database, Redis and MQ hardening

Separate DB accounts per service, grant least‑privilege, enforce TLS, enable audit logs.

Redis: no public exposure, enable ACLs, apply cache‑penetration and avalanche protection.

MQ: distinct producer/consumer accounts, per‑service topics, message signing, dead‑letter queues.

Supply‑chain security

Lock dependency versions, run OWASP Dependency‑Check, generate SBOM, scan images with Trivy/Grype, sign images with Cosign, and enforce admission control.

<build>
  <plugins>
    <plugin>
      <groupId>org.owasp</groupId>
      <artifactId>dependency-check-maven</artifactId>
      <version>10.0.4</version>
      <executions>
        <execution>
          <goals>
            <goal>check</goal>
          </goals>
        </execution>
      </executions>
      <configuration>
        <failBuildOnCVSS>7</failBuildOnCVSS>
        <formats>
          <format>HTML</format>
          <format>JSON</format>
        </formats>
      </configuration>
    </plugin>
  </plugins>
</build>
name: secure-build
on:
  push:
    branches: ["main"]
jobs:
  build-and-scan:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      packages: write
      security-events: write
      id-token: write
    steps:
    - name: Checkout
      uses: actions/checkout@v4
    - name: Setup JDK
      uses: actions/setup-java@v4
      with:
        distribution: temurin
        java-version: 21
        cache: maven
    - name: Build
      run: mvn -B clean verify
    - name: Dependency Check
      run: mvn -B org.owasp:dependency-check-maven:check
    - name: Build Image
      run: docker build -t registry.example.com/order-service:${{ github.sha }} .
    - name: Trivy Scan
      uses: aquasecurity/[email protected]
      with:
        image-ref: registry.example.com/order-service:${{ github.sha }}
        format: sarif
        output: trivy-results.sarif
    - name: Upload SARIF
      uses: github/codeql-action/upload-sarif@v3
      with:
        sarif_file: trivy-results.sarif

Audit & monitoring checklist

Structured audit logs must contain traceId, requestId, userId, tenantId, clientIp, endpoint, resourceId, outcome, latency and risk tags. Monitor login failures, token validation failures, 403/429/5xx ratios, per‑IP request peaks, slow‑request ratios, circuit‑breaker trips, blacklist hits and illegal actuator accesses. Define alert rules for brute‑force login, privilege escalation, resource exhaustion, management‑plane access and supply‑chain vulnerabilities.

Pre‑deployment checklist

Application config: minimal actuator exposure, disabled debug/Swagger, proper request limits.

Identity & permissions: OAuth2/OIDC, mandatory JWT claims, resource‑level RBAC, no default accounts.

Secrets: never in code, use Vault/KMS, support rotation, no secret leakage in logs.

Container & K8s: non‑root, drop capabilities, readOnlyRootFilesystem, disable SA token auto‑mount, enforce NetworkPolicy.

Traffic governance: WAF, gateway rate limiting, separate core vs non‑core pools.

Supply‑chain: dependency & image scanning, SBOM, image signing, minimal CI credentials.

Audit & monitoring: structured logs, traceability, security alerts, regular drills.

Following this end‑to‑end process transforms a simple Spring Boot monolith into a hardened, cloud‑native, zero‑trust production system.

Original Source

Signed-in readers can open the original source through BestHub's protected redirect.

Sign in to view source
Republication Notice

This article has been distilled and summarized from source material, then republished for learning and reference. If you believe it infringes your rights, please contactadmin@besthub.devand we will review it promptly.

Kubernetessupply-chainSpring Bootcontainer-securityauthenticationauthorizationzero-trustdeployment-security
Cloud Architecture
Written by

Cloud Architecture

Focuses on cloud‑native and distributed architecture engineering, sharing practical solutions and lessons learned. Covers microservice governance, Kubernetes, observability, and stability engineering to help your systems run stable, fast, and cost‑effectively.

0 followers
Reader feedback

How this landed with the community

Sign in to like

Rate this article

Was this worth your time?

Sign in to rate
Discussion

0 Comments

Thoughtful readers leave field notes, pushback, and hard-won operational detail here.