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 b9b766a14d..d6241a0fbe 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 @@ -33,7 +33,6 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedA 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; @@ -75,7 +74,6 @@ import org.springframework.security.web.context.RequestAttributeSecurityContextR * * @author Rob Winch * @author Ngoc Nhan - * @author Max Batischev * @since 3.2 */ public final class X509Configurer> @@ -163,38 +161,17 @@ public final class X509Configurer> * @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 Please use {{@link #x509PrincipalExtractor(X509PrincipalExtractor)} + * 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 9b6ee210ac..f997e5fa5e 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-2025 the original author or authors. + * Copyright 2002-2023 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,7 +57,6 @@ import org.springframework.security.web.authentication.preauth.PreAuthenticatedG 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; @@ -523,25 +522,12 @@ final class AuthenticationConfigBuilder { 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/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt b/config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt index f36897604a..514ace50b3 100644 --- a/config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt +++ b/config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt @@ -51,6 +51,7 @@ class X509Dsl { var authenticationDetailsSource: AuthenticationDetailsSource? = null var userDetailsService: UserDetailsService? = null var authenticationUserDetailsService: AuthenticationUserDetailsService? = null + @Deprecated("Use x509PrincipalExtractor instead") var subjectPrincipalRegex: String? = null 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 1236a2aec5..bbf8622dfe 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 @@ -1053,9 +1053,6 @@ 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 ed82f3468b..2e3d6cf275 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 @@ -2917,12 +2917,6 @@ - - - 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 38983691e1..63a3a963df 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-2025 the original author or authors. + * Copyright 2002-2023 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. @@ -43,7 +43,9 @@ import org.springframework.security.core.userdetails.UserDetailsService; import org.springframework.security.provisioning.InMemoryUserDetailsManager; import org.springframework.security.web.SecurityFilterChain; import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken; +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.X509TestUtils; import org.springframework.test.web.servlet.MockMvc; import static org.assertj.core.api.Assertions.assertThat; @@ -123,16 +125,6 @@ public class X509ConfigurerTests { // @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(); @@ -165,6 +157,28 @@ public class X509ConfigurerTests { // @formatter:on } + @Test + public void x509WhenSubjectX500PrincipalExtractor() throws Exception { + this.spring.register(SubjectX500PrincipalExtractorConfig.class).autowire(); + X509Certificate certificate = loadCert("rod.cer"); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) + .andExpect(authenticated().withUsername("rod")); + // @formatter:on + } + + @Test + public void x509WhenSubjectX500PrincipalExtractorBean() throws Exception { + this.spring.register(SubjectX500PrincipalExtractorEmailConfig.class).autowire(); + X509Certificate certificate = X509TestUtils.buildTestCertificate(); + // @formatter:off + this.mvc.perform(get("/").with(x509(certificate))) + .andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull()) + .andExpect(authenticated().withUsername("luke@monkeymachine")); + // @formatter:on + } + private T loadCert(String location) { try (InputStream is = new ClassPathResource(location).getInputStream()) { CertificateFactory certFactory = CertificateFactory.getInstance("X.509"); @@ -287,33 +301,6 @@ public class X509ConfigurerTests { } - @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 { @@ -397,4 +384,60 @@ public class X509ConfigurerTests { } + @Configuration + @EnableWebSecurity + static class SubjectX500PrincipalExtractorConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + // @formatter:off + http + .x509((x509) -> x509 + .x509PrincipalExtractor(new SubjectX500PrincipalExtractor()) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("rod") + .password("password") + .roles("USER", "ADMIN") + .build(); + return new InMemoryUserDetailsManager(user); + } + + } + + @Configuration + @EnableWebSecurity + static class SubjectX500PrincipalExtractorEmailConfig { + + @Bean + SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception { + SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor(); + principalExtractor.setExtractPrincipalNameFromEmail(true); + // @formatter:off + http + .x509((x509) -> x509 + .x509PrincipalExtractor(principalExtractor) + ); + // @formatter:on + return http.build(); + } + + @Bean + UserDetailsService userDetailsService() { + UserDetails user = User.withDefaultPasswordEncoder() + .username("luke@monkeymachine") + .password("password") + .roles("USER", "ADMIN") + .build(); + return new InMemoryUserDetailsManager(user); + } + + } + } diff --git a/config/src/test/resources/max.cer b/config/src/test/resources/max.cer deleted file mode 100644 index bd79b1f096..0000000000 --- a/config/src/test/resources/max.cer +++ /dev/null @@ -1,17 +0,0 @@ ------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 812706ae1d..8979d5ad29 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -2229,9 +2229,6 @@ 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]] ==