spring-projects/spring-security · error · BadCredentialsException

No matching pattern was found in subject DN

Error message

No matching pattern was found in subject DN: {0}

What it means

SubjectDnX509PrincipalExtractor extracts the username from an X.509 client certificate by applying a configurable regular expression to the certificate's subject DN. It throws BadCredentialsException when the regex does not match (matcher.find() returns false), because it cannot derive a principal name from the DN.

Solutions

  1. Inspect the actual subject DN by logging or running 'openssl x509 -in cert.pem -noout -subject' and adapt the regex so it matches that exact string.
  2. Set a custom regex with exactly one capturing group: extractor.setSubjectDnRegex("emailAddress=(.*?)(?:,|$)").
  3. Prefer SubjectX500PrincipalExtractor, which parses the DN via LdapName and matches on an RDN type instead of a regex.
  4. If using a custom regex, verify groupCount()==1 and that the group captures the identifier you want.

Example fix

// before
SubjectDnX509PrincipalExtractor extractor = new SubjectDnX509PrincipalExtractor(); // default CN=(.*?)(?:,|$)
// after
SubjectDnX509PrincipalExtractor extractor = new SubjectDnX509PrincipalExtractor();
extractor.setSubjectDnRegex("emailAddress=(.*?)(?:,|$)"); // matches certs whose DN has emailAddress but no CN
Defensive patterns

Strategy: validation

Validate before calling

String dn = clientCert.getSubjectX500Principal().getName(X500Principal.RFC2253);
java.util.regex.Pattern p = java.util.regex.Pattern.compile(extractorRegex);
if (!p.matcher(dn).find()) {
    throw new IllegalArgumentException("DN does not match configured regex: " + dn);
}

Type guard

boolean dnMatches(String dn, Pattern pattern) {
    return dn != null && pattern.matcher(dn).find();
}

Try / catch

try {
    return extractor.extractPrincipal(cert);
} catch (BadCredentialsException e) {
    log.warn("No principal in subject DN: {}", cert.getSubjectX500Principal());
    return null; // fall back to another authentication mechanism
}

Prevention

When it happens

Trigger: Calling extractPrincipal(cert) when the configured subjectDnRegex (e.g. the default 'CN=(.*?)(?:,|$)') does not match the certificate's getSubjectDN().getName() string — for example the CN appears in a different order, uses escaping, or the DN has no CN at all.

Common situations: Client certificates issued by a CA that places the user identifier in an attribute other than CN (e.g. emailAddress or serialNumber); internationalized/escaped DN formatting that breaks the default regex; users accidentally setting a regex without the required single capture group or testing with certificates from a different PKI.

Related errors


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

Appendix: source

Thrown at web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java:69

	protected final Log logger = LogFactory.getLog(getClass());

	protected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();

	private Pattern subjectDnPattern;

	@SuppressWarnings("NullAway") // Dataflow analysis limitation
	public SubjectDnX509PrincipalExtractor() {
		setSubjectDnRegex("CN=(.*?)(?:,|$)");
	}

	@Override
	public Object extractPrincipal(X509Certificate clientCert) {
		// String subjectDN = clientCert.getSubjectX500Principal().getName();
		String subjectDN = clientCert.getSubjectDN().getName();
		this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN));
		Matcher matcher = this.subjectDnPattern.matcher(subjectDN);
		if (!matcher.find()) {
			throw new BadCredentialsException(this.messages.getMessage("SubjectDnX509PrincipalExtractor.noMatching",
					new Object[] { subjectDN }, "No matching pattern was found in subject DN: {0}"));
		}
		Assert.isTrue(matcher.groupCount() == 1, "Regular expression must contain a single group ");
		String username = matcher.group(1);
		this.logger.debug(LogMessage.format("Extracted Principal name is '%s'", username));
		return username;
	}

	/**
	 * Sets the regular expression which will be used to extract the user name from the
	 * certificate's Subject DN.
	 * <p>
	 * It should contain a single group; for example the default expression
	 * "CN=(.*?)(?:,|$)" matches the common name field. So "CN=Jimi Hendrix, OU=..." will
	 * give a user name of "Jimi Hendrix".
	 * <p>
	 * The matches are case insensitive. So "emailAddress=(.?)," will match
	 * "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a user name "jimi@hendrix.org"

View on GitHub (pinned to 96852e8860)