X.509 Certificate Authentication
What Is X.509 Authentication?
X.509 authentication (also called mutual TLS or mTLS) uses digital certificates instead of passwords. The client presents a certificate during the TLS handshake. The server verifies the certificate against a trusted Certificate Authority (CA) and extracts the user identity from the certificate’s Common Name (CN) or Subject Alternative Name (SAN).
sequenceDiagram
participant Client as Client (with certificate)
participant Server as Spring Boot Server
Client->>Server: TLS ClientHello
Server->>Client: TLS ServerHello + Server Certificate
Server->>Client: CertificateRequest (ask for client cert)
Client->>Server: Client Certificate + CertificateVerify
Server->>Server: Verify against trusted CA
Server->>Server: Extract CN from certificate: "alice"
Server->>Server: loadUserByUsername("alice")
Server-->>Client: TLS established + authenticated
Client->>Server: GET /api/data (no password needed)
Server-->>Client: 200 OK — alice is authenticated
Use Cases
| Use case | Why X.509 |
|---|---|
| Service-to-service (microservices) | No passwords to rotate, mutual verification |
| IoT device authentication | Devices can’t type passwords; certs are pre-provisioned |
| Enterprise client authentication | Smart cards, corporate device certificates |
| High-security API clients | Stronger than passwords or API keys |
| Internal admin tools | Certificate = proof of corporate device ownership |
Spring Security Configuration
@Configuration
@EnableWebSecurity
public class X509SecurityConfig {
@Bean
public SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.x509(x509 -> x509
.subjectPrincipalRegex("CN=(.*?)(?:,|$)") // extract CN from Subject
.userDetailsService(x509UserDetailsService())
);
return http.build();
}
@Bean
public UserDetailsService x509UserDetailsService() {
// Load user from database using the CN extracted from the certificate
return username -> userRepository.findByCertificateCn(username)
.map(AppUserDetails::new)
.orElseThrow(() -> new UsernameNotFoundException(
"No user found for certificate CN: " + username
));
}
}
The subjectPrincipalRegex extracts the identity from the certificate’s Subject field. A certificate Subject looks like:
CN=alice, OU=Engineering, O=Example Corp, C=US
The regex CN=(.*?)(?:,|$) extracts alice.
Server-Side TLS Configuration
Configure Spring Boot to require client certificates:
# application.yml
server:
ssl:
enabled: true
key-store: classpath:server-keystore.p12
key-store-password: serverpass
key-store-type: PKCS12
# Trust store — CA certificates that signed client certs
trust-store: classpath:ca-truststore.p12
trust-store-password: trustpass
trust-store-type: PKCS12
# WANT: request cert but don't require it (check in Spring Security)
# NEED: require cert at TLS level (rejects before Spring Security)
client-auth: want # or "need" for strict mutual TLS
client-auth: want lets Spring Security handle the logic — useful when you want to fall back to another auth method if no certificate is provided.
client-auth: need rejects the TLS handshake if no valid client certificate is presented — the request never reaches Spring Security.
Generating Certificates for Development
# 1. Create a CA key and certificate
openssl genrsa -out ca.key 4096
openssl req -new -x509 -days 3650 -key ca.key -out ca.crt \
-subj "/C=US/O=Example Corp/CN=Example CA"
# 2. Create server key and CSR
openssl genrsa -out server.key 2048
openssl req -new -key server.key -out server.csr \
-subj "/C=US/O=Example Corp/CN=localhost"
# 3. Sign server certificate with CA
openssl x509 -req -days 365 -in server.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out server.crt
# 4. Create client key and CSR
openssl genrsa -out client.key 2048
openssl req -new -key client.key -out client.csr \
-subj "/C=US/O=Example Corp/CN=alice" # CN=alice is the username
# 5. Sign client certificate with CA
openssl x509 -req -days 365 -in client.csr -CA ca.crt -CAkey ca.key \
-CAcreateserial -out client.crt
# 6. Create server keystore (PKCS12)
openssl pkcs12 -export -out server-keystore.p12 \
-inkey server.key -in server.crt -certfile ca.crt \
-passout pass:serverpass
# 7. Create CA truststore (for verifying client certs)
keytool -importcert -file ca.crt -alias ca \
-keystore ca-truststore.p12 -storepass trustpass -storetype PKCS12 -noprompt
# 8. Create client keystore (for the client to present)
openssl pkcs12 -export -out client-keystore.p12 \
-inkey client.key -in client.crt -certfile ca.crt \
-passout pass:clientpass
Custom Principal Extractor
For more complex certificate parsing (extracting email, employee ID, or SAN):
@Component
public class CertificatePrincipalExtractor implements X509PrincipalExtractor {
@Override
public Object extractPrincipal(X509Certificate cert) {
// Option 1: extract from Subject CN
String subject = cert.getSubjectX500Principal().getName();
return extractCn(subject);
// Option 2: extract email from Subject Alternative Names
// try {
// Collection<List<?>> altNames = cert.getSubjectAlternativeNames();
// return altNames.stream()
// .filter(san -> (Integer) san.get(0) == 1) // type 1 = email
// .map(san -> (String) san.get(1))
// .findFirst()
// .orElse(extractCn(subject));
// } catch (CertificateParsingException e) {
// return extractCn(subject);
// }
}
private String extractCn(String subject) {
// Parse "CN=alice, OU=Engineering, ..." → "alice"
LdapName ln = new LdapName(subject);
return ln.getRdns().stream()
.filter(rdn -> rdn.getType().equalsIgnoreCase("CN"))
.map(rdn -> rdn.getValue().toString())
.findFirst()
.orElseThrow(() -> new IllegalArgumentException("No CN in certificate: " + subject));
}
}
http.x509(x509 -> x509
.x509PrincipalExtractor(certificatePrincipalExtractor)
.userDetailsService(x509UserDetailsService())
);
Combining X.509 with JWT (Service-to-Service)
A common microservice pattern: service clients use certificates for mutual authentication, and the gateway/auth service issues short-lived JWTs:
flowchart LR
Client[Service A\nwith client cert] -->|mTLS| GW[API Gateway\nverify cert → issue JWT]
GW -->|JWT| SB[Service B\nverify JWT]
GW -->|JWT| SC[Service C\nverify JWT]
@Configuration
public class ServiceSecurityConfig {
@Bean
@Order(1)
public SecurityFilterChain serviceChain(HttpSecurity http) throws Exception {
http
.securityMatcher("/internal/**")
.x509(x509 -> x509
.subjectPrincipalRegex("CN=(.*?)(?:,|$)")
.userDetailsService(serviceAccountDetailsService())
)
.authorizeHttpRequests(auth -> auth
.requestMatchers("/internal/**").hasRole("SERVICE")
)
.sessionManagement(s -> s.sessionCreationPolicy(SessionCreationPolicy.STATELESS));
return http.build();
}
}
Testing X.509 Authentication
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
class X509AuthTest {
@Autowired
private TestRestTemplate restTemplate;
@Test
void withValidCertificate_shouldAuthenticate() throws Exception {
// Load client keystore
KeyStore clientKs = KeyStore.getInstance("PKCS12");
try (InputStream ks = new ClassPathResource("client-keystore.p12").getInputStream()) {
clientKs.load(ks, "clientpass".toCharArray());
}
// Create SSL context with client cert
SSLContext sslContext = SSLContextBuilder.create()
.loadKeyMaterial(clientKs, "clientpass".toCharArray())
.loadTrustMaterial(trustStore, null)
.build();
CloseableHttpClient httpClient = HttpClients.custom()
.setSSLContext(sslContext)
.build();
ResponseEntity<String> response = new RestTemplate(
new HttpComponentsClientHttpRequestFactory(httpClient)
).getForEntity("https://localhost:" + port + "/api/data", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.OK);
}
@Test
void withNoCertificate_shouldReturn401() {
ResponseEntity<String> response =
restTemplate.getForEntity("/api/data", String.class);
assertThat(response.getStatusCode()).isEqualTo(HttpStatus.UNAUTHORIZED);
}
}
Summary
- X.509 authentication uses client TLS certificates — no passwords, strong identity.
- Configure
server.ssl.client-auth: want/needinapplication.ymlto enable client certificate requests. - Use
http.x509()in Spring Security to extract the principal from the certificate CN and loadUserDetails. - Use
wantfor flexible auth (fall back to other mechanisms); useneedfor strict mTLS. - Generate development certificates with OpenSSL; use a proper CA (e.g., HashiCorp Vault PKI) in production.
- Best use cases: microservice mTLS, IoT devices, enterprise client authentication.
Next: Article 12 covers OAuth2 fundamentals — grant types, flows, and the terminology you need before implementing OAuth2 login or resource server.