{"record":{"id":"b5a804b3b9f7cb1c","repo":"spring-projects/spring-security","slug":"no-matching-pattern-was-found-in-subject-dn-0","errorCode":null,"errorMessage":"No matching pattern was found in subject DN: {0}","messagePattern":"No matching pattern was found in subject DN: (.+?)","errorType":"exception","errorClass":"BadCredentialsException","httpStatus":null,"severity":"error","filePath":"web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java","lineNumber":69,"sourceCode":"\tprotected final Log logger = LogFactory.getLog(getClass());\n\n\tprotected MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor();\n\n\tprivate Pattern subjectDnPattern;\n\n\t@SuppressWarnings(\"NullAway\") // Dataflow analysis limitation\n\tpublic SubjectDnX509PrincipalExtractor() {\n\t\tsetSubjectDnRegex(\"CN=(.*?)(?:,|$)\");\n\t}\n\n\t@Override\n\tpublic Object extractPrincipal(X509Certificate clientCert) {\n\t\t// String subjectDN = clientCert.getSubjectX500Principal().getName();\n\t\tString subjectDN = clientCert.getSubjectDN().getName();\n\t\tthis.logger.debug(LogMessage.format(\"Subject DN is '%s'\", subjectDN));\n\t\tMatcher matcher = this.subjectDnPattern.matcher(subjectDN);\n\t\tif (!matcher.find()) {\n\t\t\tthrow new BadCredentialsException(this.messages.getMessage(\"SubjectDnX509PrincipalExtractor.noMatching\",\n\t\t\t\t\tnew Object[] { subjectDN }, \"No matching pattern was found in subject DN: {0}\"));\n\t\t}\n\t\tAssert.isTrue(matcher.groupCount() == 1, \"Regular expression must contain a single group \");\n\t\tString username = matcher.group(1);\n\t\tthis.logger.debug(LogMessage.format(\"Extracted Principal name is '%s'\", username));\n\t\treturn username;\n\t}\n\n\t/**\n\t * Sets the regular expression which will be used to extract the user name from the\n\t * certificate's Subject DN.\n\t * <p>\n\t * It should contain a single group; for example the default expression\n\t * \"CN=(.*?)(?:,|$)\" matches the common name field. So \"CN=Jimi Hendrix, OU=...\" will\n\t * give a user name of \"Jimi Hendrix\".\n\t * <p>\n\t * The matches are case insensitive. So \"emailAddress=(.?),\" will match\n\t * \"EMAILADDRESS=jimi@hendrix.org, CN=...\" giving a user name \"jimi@hendrix.org\"","sourceCodeStart":51,"sourceCodeEnd":87,"githubUrl":"https://github.com/spring-projects/spring-security/blob/96852e8860138a482cb13d1479573f24ff6443c6/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java#L51-L87","documentation":"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.","triggerScenarios":"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.","commonSituations":"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.","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."],"exampleFix":"// before\nSubjectDnX509PrincipalExtractor extractor = new SubjectDnX509PrincipalExtractor(); // default CN=(.*?)(?:,|$)\n// after\nSubjectDnX509PrincipalExtractor extractor = new SubjectDnX509PrincipalExtractor();\nextractor.setSubjectDnRegex(\"emailAddress=(.*?)(?:,|$)\"); // matches certs whose DN has emailAddress but no CN","handlingStrategy":"validation","validationCode":"String dn = clientCert.getSubjectX500Principal().getName(X500Principal.RFC2253);\njava.util.regex.Pattern p = java.util.regex.Pattern.compile(extractorRegex);\nif (!p.matcher(dn).find()) {\n    throw new IllegalArgumentException(\"DN does not match configured regex: \" + dn);\n}","typeGuard":"boolean dnMatches(String dn, Pattern pattern) {\n    return dn != null && pattern.matcher(dn).find();\n}","tryCatchPattern":"try {\n    return extractor.extractPrincipal(cert);\n} catch (BadCredentialsException e) {\n    log.warn(\"No principal in subject DN: {}\", cert.getSubjectX500Principal());\n    return null; // fall back to another authentication mechanism\n}","preventionTips":["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"],"tags":["x509","authentication","regex","spring-security"],"backgroundTag":"regex-does-not-match","analyzedSha":"96852e8860138a482cb13d1479573f24ff6443c6","analyzedAt":"2026-09-10T23:25:23.477Z","contentChangedAt":"2026-09-10T23:25:23.477Z","schemaVersion":2},"datasetVersion":"2026-09-23T08:17:48.524Z"}