Просмотр исходного кода

Use principalExtractor reference instead of properties

Rob Winch 2 месяцев назад
Родитель
Сommit
88ed4a5ccf

+ 2 - 25
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<H extends HttpSecurityBuilder<H>>
@@ -163,38 +161,17 @@ public final class X509Configurer<H extends HttpSecurityBuilder<H>>
 	 * @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<H> 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<H> 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();

+ 1 - 15
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);

+ 1 - 0
config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt

@@ -51,6 +51,7 @@ class X509Dsl {
     var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails>? = null
     var userDetailsService: UserDetailsService? = null
     var authenticationUserDetailsService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>? = null
+    @Deprecated("Use x509PrincipalExtractor instead")
     var subjectPrincipalRegex: String? = null
 
 

+ 0 - 3
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.

+ 0 - 6
config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd

@@ -2917,12 +2917,6 @@
                 </xs:documentation>
          </xs:annotation>
       </xs:attribute>
-      <xs:attribute name="extract-principal-name-from-email" type="xs:token">
-         <xs:annotation>
-            <xs:documentation>If true then DN will be extracted from EMAIlADDRESS
-                </xs:documentation>
-         </xs:annotation>
-      </xs:attribute>
   </xs:attributeGroup>
   <xs:element name="jee">
       <xs:annotation>

+ 81 - 38
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 extends Certificate> 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);
+		}
+
+	}
+
 }

+ 0 - 17
config/src/test/resources/max.cer

@@ -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-----

+ 0 - 3
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]]
 == <filter-chain-map>