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
- 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.
- Set a custom regex with exactly one capturing group: extractor.setSubjectDnRegex("emailAddress=(.*?)(?:,|$)").
- Prefer SubjectX500PrincipalExtractor, which parses the DN via LdapName and matches on an RDN type instead of a regex.
- 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
- Log or inspect the actual subject DN (openssl x509 -noout -subject) before choosing a regex
- Keep exactly one capturing group in the regex
- Prefer SubjectX500PrincipalExtractor (type-based RDN match) over regex matching
- Test the extractor with certificates from your real CA, not synthetic ones
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
- Failed to parse client certificate
- No matching pattern was found in subject DN
- AccountStatusUserDetailsChecker.disabled
- AccountStatusUserDetailsChecker.expired
- An Authentication object was not found in the…
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)