How do you configure mutual TLS (client certificate authentication) on the embedded server, and how does it relate to SSL bundles?
answer
- client-auth: NONE / WANT / NEED
- NEED = handshake fails without valid client cert
- truststore holds trusted client CA (bundle.truststore preferred)
- X509Certificate attribute -> Spring Security x509()
- NEED is per-connector; mixed audiences need two connectors
basics
~10 sSet server.ssl.client-auth to NEED (mandatory) or WANT (optional), and supply a truststore of accepted client-CA certificates via server.ssl.trust-store (or a bundle's truststore). The server then requests and validates the client's certificate during the handshake.
solid answer
~40 sMutual TLS makes the client also present a certificate. On the embedded connector you set server.ssl.client-auth: NEED to require it (handshake fails without a valid client cert) or WANT to request but not require it, plus NONE (default) to disable. Validation needs trust material: either server.ssl.trust-store / trust-store-password, or, preferably, a bundle's truststore (spring.ssl.bundle.pem.<name>.truststore.certificate points at the client CA). The bundle approach means the same named material can hot-reload the CA and be reused for outbound mTLS clients. After the handshake, the verified client cert appears as the request's X509Certificate (jakarta.servlet.request.X509Certificate attribute), which Spring Security's x509() support can turn into an Authentication by extracting the subject/CN. Key design point: NEED enforces at the TLS layer before any HTTP handler runs, which is stronger than app-level auth but less flexible for mixed-audience endpoints.
code
java · 14 lines// Map the mTLS-verified client cert to a Spring Security Authentication
@Configuration
@EnableWebSecurity
class MtlsSecurityConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http.authorizeHttpRequests(a -> a.anyRequest().authenticated())
.x509(x509 -> x509
// extract principal (e.g. CN) from the client certificate subject
.subjectPrincipalRegex("CN=(.*?)(?:,|$)"));
return http.build();
}
// server.ssl.client-auth=NEED + a truststore of the client CA are set in properties/bundle
}go deeper
Know client-auth NEED/WANT/NONE requests a client certificate.
Explain the truststore of client CAs and NEED = fail-closed handshake.
Connect the verified X509Certificate to Spring Security x509() identity mapping and bundle truststore reload.
Weigh embedded vs LB/mesh mTLS termination, handle mixed audiences with dual connectors, and own CA rotation and revocation strategy.
## What mutual TLS (mTLS) is Ordinary TLS authenticates only the *server* to the client. **Mutual TLS** also authenticates the *client* to the server: during the handshake the server sends a `CertificateRequest`, the client presents its own certificate, and the server validates it against a set of trusted client CAs. It's common for service-to-service auth, zero-trust meshes, and B2B APIs. ## The three settings of `server.ssl.client-auth` - **`NONE`** (default) — no client cert requested; plain server-only TLS. - **`WANT`** — server *requests* a client cert but proceeds even if the client doesn't supply one (or supplies an untrusted one, depending on stack). Use when only some clients present certs. - **`NEED`** — server *requires* a valid, trusted client cert; the **TLS handshake fails** otherwise, before any HTTP request reaches your controllers. This is the strong, fail-closed mode. ## Supplying trust material To validate client certs the server needs a *truststore* of the client-issuing CA(s): - Legacy: `server.ssl.trust-store`, `server.ssl.trust-store-password`, `server.ssl.trust-store-type`. - **Preferred (bundles):** a bundle `truststore`, e.g. `spring.ssl.bundle.pem.<name>.truststore.certificate: file:/etc/tls/client-ca.crt`, then `server.ssl.bundle=<name>`. Benefits: the CA can hot-reload (`reload-on-update`) and the same bundle secures outbound mTLS clients too. ```yaml spring: ssl: bundle: pem: edge: keystore: certificate: "file:/etc/tls/tls.crt" private-key: "file:/etc/tls/tls.key" truststore: certificate: "file:/etc/tls/client-ca.crt" # trusted client CA reload-on-update: true server: ssl: bundle: edge client-auth: NEED ``` ## From verified cert to Spring Security identity A successful mTLS handshake exposes the client cert to the app as the servlet request attribute `jakarta.servlet.request.X509Certificate`. Spring Security's **`x509()`** DSL (`http.x509(x509 -> x509.subjectPrincipalRegex(...))`) reads it, extracts a principal (typically the certificate CN), and loads a `UserDetails` to build the `Authentication`. So TLS proves *possession of a valid cert*; Spring Security maps that to *who they are and what they may do*. ## Design considerations (principal level) - **Where to terminate.** If a load balancer/ingress terminates TLS, mTLS is usually enforced there and the client identity is forwarded via a header (e.g. `X-SSL-Client-CN`) — but then you must trust and validate that hop. Embedded mTLS keeps verification in-process, avoiding a spoofable header, at the cost of the app owning cert distribution. - **NEED vs mixed audiences.** `NEED` is all-or-nothing per connector: every request on that port must present a cert. For an app serving both public and mTLS clients you need two connectors (one `NONE`, one `NEED`) via a `WebServerFactoryCustomizer`, or split by port/service. - **Revocation.** JSSE truststore trust doesn't automatically check CRL/OCSP; revoking a compromised client cert may require CA rotation or explicit revocation checking configuration. - **CA rotation.** Rotating the client CA is exactly where bundle `reload-on-update` on the truststore pays off — you swap the trusted CA without a restart. - **WANT pitfalls.** With `WANT`, absence of a cert is allowed, so you must enforce authorization in the app; don't assume a cert was presented. ## Gotchas - Truststore must contain the **issuing CA**, not each client leaf cert (unless you deliberately pin leaves). - `NEED` failures surface as opaque TLS handshake errors to the client, not HTTP 401 — harder to debug; check server logs. - Combining bundle-based `client-auth` with a legacy `trust-store` is contradictory; keep trust material in one place. ## When to use Use embedded mTLS for internal service meshes and B2B endpoints where cryptographic client identity matters and you want fail-closed enforcement below the HTTP layer. Prefer LB/ingress termination when you already run a mesh (Istio/Linkerd) that manages certs and identity for you.
- Your app must serve public HTTP clients and mTLS-only internal clients. How do you do that on one Spring Boot app?client-auth is per-connector, so you can't mix NEED and NONE on a single connector. Run two connectors — the default one with client-auth NONE and a second one (added via a WebServerFactoryCustomizer on a different port) with client-auth NEED and the client-CA truststore — or split the audiences across services/ports.
- What's the difference between WANT and NEED, and what's the risk with WANT?NEED fails the handshake if no valid client cert is presented (fail-closed). WANT requests a cert but proceeds without one. The risk: with WANT you cannot assume a cert exists, so authorization must be enforced in the app or you effectively allow uncertified clients.
saying these in an interview costs you the question
- Thinking client-auth alone authenticates users without a truststore of the client CA.
- Assuming NEED returns HTTP 401 rather than failing the TLS handshake.
- Believing one connector can be NEED for some paths and NONE for others.
- Expecting JSSE truststore trust to automatically check revocation (CRL/OCSP).