spring-projects/spring-security · error · OAuth2AuthenticationException

invalid_client

invalid_client

Error message

Client authentication failed: x509_certificate_issuer

What it means

This error is thrown during X.509 self-signed certificate client authentication when the presented certificate's issuer DN does not equal its subject DN. A self-signed certificate must have identical issuer and subject principals; anything else is not self-signed and cannot be used with this authenticator. The server rejects the client with an invalid_client OAuth2 error naming the failing parameter 'x509_certificate_issuer'.

Source

Thrown at oauth2/oauth2-authorization-server/src/main/java/org/springframework/security/oauth2/server/authorization/authentication/X509SelfSignedCertificateVerifier.java:85

	private static final String ERROR_URI = "https://datatracker.ietf.org/doc/html/rfc6749#section-3.2.1";

	private static final JWKMatcher HAS_X509_CERT_CHAIN_MATCHER = new JWKMatcher.Builder().hasX509CertChain(true)
		.build();

	private final Function<RegisteredClient, JWKSet> jwkSetSupplier = new JwkSetSupplier();

	@Override
	public void accept(OAuth2ClientAuthenticationContext clientAuthenticationContext) {
		OAuth2ClientAuthenticationToken clientAuthentication = clientAuthenticationContext.getAuthentication();
		RegisteredClient registeredClient = clientAuthenticationContext.getRegisteredClient();
		X509Certificate[] clientCertificateChain = (X509Certificate[]) clientAuthentication.getCredentials();
		Assert.notEmpty(clientCertificateChain, "clientCertificateChain cannot be empty");
		X509Certificate clientCertificate = clientCertificateChain[0];

		X500Principal issuer = clientCertificate.getIssuerX500Principal();
		X500Principal subject = clientCertificate.getSubjectX500Principal();
		if (issuer == null || !issuer.equals(subject)) {
			throw invalidClient("x509_certificate_issuer");
		}

		JWKSet jwkSet = this.jwkSetSupplier.apply(registeredClient);

		boolean publicKeyMatches = false;
		for (JWK jwk : jwkSet.filter(HAS_X509_CERT_CHAIN_MATCHER).getKeys()) {
			X509Certificate x509Certificate = jwk.getParsedX509CertChain().get(0);
			PublicKey publicKey = x509Certificate.getPublicKey();
			if (Arrays.equals(clientCertificate.getPublicKey().getEncoded(), publicKey.getEncoded())) {
				publicKeyMatches = true;
				break;
			}
		}

		if (!publicKeyMatches) {
			throw invalidClient("x509_certificate");
		}
	}

View on GitHub (pinned to 96852e8860)

Solutions

  1. Generate a truly self-signed certificate (keytool -genkeypair -keyalg RSA without -issuer, or openssl req -x509) and register it in the client's JWK Set via the jwk_set_url client setting
  2. Verify the certificate with: cert.getIssuerX500Principal().equals(cert.getSubjectX500Principal()) before configuring it
  3. If you need CA-issued certificates, use a client authentication method that supports them instead of the self-signed verifier

Example fix

// before
certificate issued by Corporate CA -> presented as self-signed
// after
keytool -genkeypair -alias client -keyalg RSA -keystore client.p12 -dname "CN=client"  # self-signed by construction
Defensive patterns

Strategy: validation

Validate before calling

if (cert.getIssuerX500Principal() == null || !cert.getIssuerX500Principal().equals(cert.getSubjectX500Principal())) {
    throw new IllegalArgumentException("certificate is not self-signed: issuer != subject");
}

Try / catch

try {
    authenticator.accept(context);
} catch (OAuth2AuthenticationException ex) {
    // check ex.getError().getErrorCode() == "invalid_client" and description for x509_certificate_issuer
}

Prevention

When it happens

Trigger: Calling OAuth2 client authentication with an X.509 certificate chain whose first certificate has issuer != subject, e.g. a CA-signed (end-entity) certificate submitted to X509SelfSignedCertificateVerifier.

Common situations: Developers configure a certificate issued by an internal CA (or a chained PKCS#12 entry) but register the client with a self-signed-certificate client authentication method; also happens when the wrong entry from a keystore is presented instead of the self-signed one.

Understand the failure class

Related errors


AI-assisted analysis of spring-projects/spring-security@96852e8860 (2026-09-10). Data as JSON: /api/errors/42972ed05247e1cd. Report an issue: GitHub.