diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java index a3818e2a9ac..679838927cf 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2024 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -33,9 +33,11 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.context.RequestAttributeSecurityContextRepository; +import org.springframework.util.Assert; /** * Adds X509 based pre authentication to an application. Since validating the certificate @@ -74,6 +76,7 @@ * * @author Rob Winch * @author Ngoc Nhan + * @author Max Batischev * @since 3.2 */ public final class X509Configurer> @@ -103,6 +106,7 @@ public X509Configurer() { * @return the {@link X509Configurer} for further customizations */ public X509Configurer x509AuthenticationFilter(X509AuthenticationFilter x509AuthenticationFilter) { + Assert.notNull(x509AuthenticationFilter, "x509AuthenticationFilter cannot be null"); this.x509AuthenticationFilter = x509AuthenticationFilter; return this; } @@ -113,6 +117,7 @@ public X509Configurer x509AuthenticationFilter(X509AuthenticationFilter x509A * @return the {@link X509Configurer} to use */ public X509Configurer x509PrincipalExtractor(X509PrincipalExtractor x509PrincipalExtractor) { + Assert.notNull(x509PrincipalExtractor, "x509PrincipalExtractor cannot be null"); this.x509PrincipalExtractor = x509PrincipalExtractor; return this; } @@ -124,6 +129,7 @@ public X509Configurer x509PrincipalExtractor(X509PrincipalExtractor x509Princ */ public X509Configurer authenticationDetailsSource( AuthenticationDetailsSource authenticationDetailsSource) { + Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); this.authenticationDetailsSource = authenticationDetailsSource; return this; } @@ -150,6 +156,7 @@ public X509Configurer userDetailsService(UserDetailsService userDetailsServic */ public X509Configurer authenticationUserDetailsService( AuthenticationUserDetailsService authenticationUserDetailsService) { + Assert.notNull(authenticationUserDetailsService, "authenticationUserDetailsService cannot be null"); this.authenticationUserDetailsService = authenticationUserDetailsService; return this; } @@ -161,14 +168,38 @@ public X509Configurer authenticationUserDetailsService( * @param subjectPrincipalRegex the regex to extract the user principal from the * certificate (i.e. "CN=(.*?)(?:,|$)"). * @return the {@link X509Configurer} for further customizations + * @deprecated Please use {{@link #extractPrincipalNameFromEmail(boolean)}} instead */ + @Deprecated public X509Configurer subjectPrincipalRegex(String subjectPrincipalRegex) { + if (this.x509PrincipalExtractor instanceof SubjectX500PrincipalExtractor) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); principalExtractor.setSubjectDnRegex(subjectPrincipalRegex); this.x509PrincipalExtractor = principalExtractor; return this; } + /** + * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} + * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS + * @since 7.0 + */ + public X509Configurer extractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { + if (this.x509PrincipalExtractor instanceof SubjectDnX509PrincipalExtractor) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } + SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor(); + extractor.setExtractPrincipalNameFromEmail(extractPrincipalNameFromEmail); + this.x509PrincipalExtractor = extractor; + return this; + } + @Override public void init(H http) { PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider(); diff --git a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java index f997e5fa5e5..9b6ee210acc 100644 --- a/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java +++ b/config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -57,6 +57,7 @@ import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource; import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter; import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter; import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter; import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter; @@ -522,12 +523,25 @@ void createX509Filter(BeanReference authManager, filterBuilder.addPropertyValue("securityContextHolderStrategy", authenticationFilterSecurityContextHolderStrategyRef); String regex = x509Elt.getAttribute("subject-principal-regex"); + String extractPrincipalNameFromEmail = x509Elt.getAttribute("extract-principal-name-from-email"); + if (StringUtils.hasText(regex) && StringUtils.hasText(extractPrincipalNameFromEmail)) { + throw new IllegalStateException( + "Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. " + + "Please use one or the other."); + } if (StringUtils.hasText(regex)) { BeanDefinitionBuilder extractor = BeanDefinitionBuilder .rootBeanDefinition(SubjectDnX509PrincipalExtractor.class); extractor.addPropertyValue("subjectDnRegex", regex); filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); } + if (StringUtils.hasText(extractPrincipalNameFromEmail)) { + BeanDefinitionBuilder extractor = BeanDefinitionBuilder + .rootBeanDefinition(SubjectX500PrincipalExtractor.class); + extractor.addPropertyValue("extractPrincipalNameFromEmail", + Boolean.parseBoolean(extractPrincipalNameFromEmail)); + filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition()); + } injectAuthenticationDetailsSource(x509Elt, filterBuilder); filter = (RootBeanDefinition) filterBuilder.getBeanDefinition(); createPrauthEntryPoint(x509Elt); diff --git a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java index a6d77b3683c..ed5159d626a 100644 --- a/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java +++ b/config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java @@ -119,7 +119,7 @@ import org.springframework.security.oauth2.server.resource.web.server.authentication.ServerBearerTokenAuthenticationConverter; import org.springframework.security.web.PortMapper; import org.springframework.security.web.authentication.logout.LogoutHandler; -import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor; +import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor; import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor; import org.springframework.security.web.server.DefaultServerRedirectStrategy; import org.springframework.security.web.server.DelegatingServerAuthenticationEntryPoint; @@ -943,8 +943,8 @@ public ServerHttpSecurity formLogin(Customizer formLoginCustomize * } * * - * Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} - * will be used. If authenticationManager is not specified, + * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will + * be used. If authenticationManager is not specified, * {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * @return the {@link X509Spec} to customize * @since 5.2 @@ -978,8 +978,8 @@ public X509Spec x509() { * } * * - * Note that if extractor is not specified, {@link SubjectDnX509PrincipalExtractor} - * will be used. If authenticationManager is not specified, + * Note that if extractor is not specified, {@link SubjectX500PrincipalExtractor} will + * be used. If authenticationManager is not specified, * {@link ReactivePreAuthenticatedAuthenticationManager} will be used. * @param x509Customizer the {@link Customizer} to provide more options for the * {@link X509Spec} @@ -4180,7 +4180,7 @@ private X509PrincipalExtractor getPrincipalExtractor() { if (this.principalExtractor != null) { return this.principalExtractor; } - return new SubjectDnX509PrincipalExtractor(); + return new SubjectX500PrincipalExtractor(); } private ReactiveAuthenticationManager getAuthenticationManager() { diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc index ec51246b6fe..6bd2b2ffe24 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -1050,6 +1050,9 @@ x509.attlist &= x509.attlist &= ## Reference to an AuthenticationDetailsSource which will be used by the authentication filter attribute authentication-details-source-ref {xsd:token}? +x509.attlist &= + ## If true then DN will be extracted from EMAIlADDRESS + attribute extract-principal-name-from-email {xsd:token}? jee = ## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication. diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index e254b8488ea..7323a875c1b 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -2911,6 +2911,12 @@ + + + If true then DN will be extracted from EMAIlADDRESS + + + diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java index 206c0b7ecee..38983691e15 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2023 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -123,6 +123,16 @@ public void x509WhenSubjectPrincipalRegexInLambdaThenUsesRegexToExtractPrincipal // @formatter:on } + @Test + public void x509WhenExtractPrincipalNameFromEmailIsTrueThenUsesEmailAddressToExtractPrincipal() throws Exception { + this.spring.register(EmailPrincipalConfig.class).autowire(); + X509Certificate certificate = loadCert("max.cer"); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect(authenticated().withUsername("maxbatischev@gmail.com")); + // @formatter:on + } + @Test public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception { this.spring.register(UserDetailsServiceBeanConfig.class).autowire(); @@ -277,6 +287,33 @@ UserDetailsService userDetailsService() { } + @Configuration + @EnableWebSecurity + static class EmailPrincipalConfig { + + @Bean + SecurityFilterChain filterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509((x509) -> + x509.extractPrincipalNameFromEmail(true) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("maxbatischev@gmail.com") + .password("password") + .roles("USER", "ADMIN") + .build(); + return new InMemoryUserDetailsManager(user); + } + + } + @Configuration @EnableWebSecurity static class UserDetailsServiceBeanConfig { diff --git a/config/src/test/resources/max.cer b/config/src/test/resources/max.cer new file mode 100644 index 00000000000..bd79b1f096b --- /dev/null +++ b/config/src/test/resources/max.cer @@ -0,0 +1,17 @@ +-----BEGIN CERTIFICATE----- +MIICojCCAgugAwIBAgIBADANBgkqhkiG9w0BAQ0FADBuMQswCQYDVQQGEwJydTEP +MA0GA1UECAwGTW9zY293MQ8wDQYDVQQKDAZTcHJpbmcxFjAUBgNVBAMMDU1heCBC +YXRpc2NoZXYxJTAjBgkqhkiG9w0BCQEWFm1heGJhdGlzY2hldkBnbWFpbC5jb20w +HhcNMjUwNTE0MTcyODM5WhcNMjYwNTE0MTcyODM5WjBuMQswCQYDVQQGEwJydTEP +MA0GA1UECAwGTW9zY293MQ8wDQYDVQQKDAZTcHJpbmcxFjAUBgNVBAMMDU1heCBC +YXRpc2NoZXYxJTAjBgkqhkiG9w0BCQEWFm1heGJhdGlzY2hldkBnbWFpbC5jb20w +gZ8wDQYJKoZIhvcNAQEBBQADgY0AMIGJAoGBALVZ2K/iOINeHZ4XAV3QmNRgS+iB +Vw0fW07uzYkCoSZU1lOBQE0k8+fdM2+X9AsgwfRCE3tUZquPApEKynB5V9Seh+bR +vc9aj7PunMyN+zjRU6X7/BL3VqLfrJLSc15bQaSN1phJ6NT+BTXPTuiPbXldnJLC +wVo6PView83yZ335AgMBAAGjUDBOMB0GA1UdDgQWBBQhyQfxL2ZYotcS8AmMJtli +2IRAMTAfBgNVHSMEGDAWgBQhyQfxL2ZYotcS8AmMJtli2IRAMTAMBgNVHRMEBTAD +AQH/MA0GCSqGSIb3DQEBDQUAA4GBAIIIJxpsTPtUEnePAqqgVFWDKC2CExhtCBYL +MjLSC+7E9OlfuuX1joAsD4Yv86k4Ox836D0KQtINtg3y6D8O+HSylhVg1xtOiK7l +ElXVRepB8GcX3vf9F58v9s++cSDvXf8vJu/O7nI4fv9C5SfUtMY4JPh/3MTsyl8O +tgxTKjvO +-----END CERTIFICATE----- diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index 2b434d43030..302e419580e 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -2223,6 +2223,9 @@ Defines a regular expression which will be used to extract the username from the Allows a specific `UserDetailsService` to be used with X.509 in the case where multiple instances are configured. If not set, an attempt will be made to locate a suitable instance automatically and use that. +[[nsa-x509-extract-principal-name-from-email]] +* **extract-principal-name-from-email** +If true then DN will be extracted from EMAIlADDRESS. [[nsa-filter-chain-map]] == diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java index b690af59c03..ffa217d8d52 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectDnX509PrincipalExtractor.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2020 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,6 +16,7 @@ package org.springframework.security.web.authentication.preauth.x509; +import java.security.Principal; import java.security.cert.X509Certificate; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -43,7 +44,9 @@ * "EMAILADDRESS=jimi@hendrix.org, CN=..." giving a user name "jimi@hendrix.org" * * @author Luke Taylor + * @deprecated Please use {@link SubjectX500PrincipalExtractor} instead */ +@Deprecated public class SubjectDnX509PrincipalExtractor implements X509PrincipalExtractor, MessageSourceAware { protected final Log logger = LogFactory.getLog(getClass()); @@ -59,6 +62,7 @@ public SubjectDnX509PrincipalExtractor() { @Override public Object extractPrincipal(X509Certificate clientCert) { // String subjectDN = clientCert.getSubjectX500Principal().getName(); + Principal principal = clientCert.getSubjectDN(); String subjectDN = clientCert.getSubjectDN().getName(); this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); Matcher matcher = this.subjectDnPattern.matcher(subjectDN); diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java new file mode 100644 index 00000000000..4b735c05287 --- /dev/null +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractor.java @@ -0,0 +1,88 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication.preauth.x509; + +import java.security.cert.X509Certificate; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import javax.security.auth.x500.X500Principal; + +import org.apache.commons.logging.Log; +import org.apache.commons.logging.LogFactory; + +import org.springframework.context.MessageSource; +import org.springframework.context.MessageSourceAware; +import org.springframework.context.support.MessageSourceAccessor; +import org.springframework.core.log.LogMessage; +import org.springframework.security.authentication.BadCredentialsException; +import org.springframework.security.core.SpringSecurityMessageSource; +import org.springframework.util.Assert; + +/** + * Obtains the principal from a certificate using RFC2253 and RFC1779 formats. By default, + * RFC2253 is used: DN is extracted from CN. If extractPrincipalNameFromEmail is true then + * format RFC1779 will be used: DN is extracted from EMAIlADDRESS. + * + * @author Max Batischev + * @since 7.0 + */ +public final class SubjectX500PrincipalExtractor implements X509PrincipalExtractor, MessageSourceAware { + + private final Log logger = LogFactory.getLog(getClass()); + + private MessageSourceAccessor messages = SpringSecurityMessageSource.getAccessor(); + + private boolean extractPrincipalNameFromEmail = false; + + private final Pattern cnSubjectDnPattern = Pattern.compile("CN=(.*?)(?:,|$)", Pattern.CASE_INSENSITIVE); + + private final Pattern emailSubjectDnPattern = Pattern.compile("OID.1.2.840.113549.1.9.1=(.*?)(?:,|$)", + Pattern.CASE_INSENSITIVE); + + @Override + public Object extractPrincipal(X509Certificate clientCert) { + Assert.notNull(clientCert, "clientCert cannot be null"); + X500Principal principal = clientCert.getSubjectX500Principal(); + String subjectDN = this.extractPrincipalNameFromEmail ? principal.getName("RFC1779") : principal.getName(); + this.logger.debug(LogMessage.format("Subject DN is '%s'", subjectDN)); + Matcher matcher = this.extractPrincipalNameFromEmail ? this.emailSubjectDnPattern.matcher(subjectDN) + : this.cnSubjectDnPattern.matcher(subjectDN); + if (!matcher.find()) { + throw new BadCredentialsException(this.messages.getMessage("SubjectX500PrincipalExtractor.noMatching", + new Object[] { subjectDN }, "No matching pattern was found in subject DN: {0}")); + } + String principalName = matcher.group(1); + this.logger.debug(LogMessage.format("Extracted Principal name is '%s'", principalName)); + return principalName; + } + + @Override + public void setMessageSource(MessageSource messageSource) { + Assert.notNull(messageSource, "messageSource cannot be null"); + this.messages = new MessageSourceAccessor(messageSource); + } + + /** + * If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false} + * @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS + */ + public void setExtractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) { + this.extractPrincipalNameFromEmail = extractPrincipalNameFromEmail; + } + +} diff --git a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java index c49cc6ab2f6..02618d126db 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java +++ b/web/src/main/java/org/springframework/security/web/authentication/preauth/x509/X509AuthenticationFilter.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2016 the original author or authors. + * Copyright 2002-2025 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -28,7 +28,7 @@ */ public class X509AuthenticationFilter extends AbstractPreAuthenticatedProcessingFilter { - private X509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor(); + private X509PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); @Override protected Object getPreAuthenticatedPrincipal(HttpServletRequest request) { diff --git a/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java b/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java new file mode 100644 index 00000000000..ebc1064fdc3 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/authentication/preauth/x509/SubjectX500PrincipalExtractorTests.java @@ -0,0 +1,66 @@ +/* + * Copyright 2002-2025 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication.preauth.x509; + +import org.junit.jupiter.api.Test; + +import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; + +/** + * Tests for {@link SubjectX500PrincipalExtractor}. + * + * @author Max Batischev + */ +public class SubjectX500PrincipalExtractorTests { + + private final SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor(); + + @Test + void extractWhenCnPatternSetThenExtractsPrincipalName() throws Exception { + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificate()); + + assertThat(principal).isEqualTo("Luke Taylor"); + } + + @Test + void extractWhenEmailPatternSetThenExtractsPrincipalName() throws Exception { + this.extractor.setExtractPrincipalNameFromEmail(true); + + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificate()); + + assertThat(principal).isEqualTo("luke@monkeymachine"); + } + + @Test + void extractWhenCnAtEndThenExtractsPrincipalName() throws Exception { + Object principal = this.extractor.extractPrincipal(X509TestUtils.buildTestCertificateWithCnAtEnd()); + + assertThat(principal).isEqualTo("Duke"); + } + + @Test + void setMessageSourceWhenNullThenThrowsException() { + assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.setMessageSource(null)); + } + + @Test + void extractWhenCertificateIsNullThenFails() { + assertThatIllegalArgumentException().isThrownBy(() -> this.extractor.extractPrincipal(null)); + } + +}