Skip to content

Commit 88ed4a5

Browse files
committed
Use principalExtractor reference instead of properties
1 parent 2b740b7 commit 88ed4a5

File tree

8 files changed

+85
-107
lines changed

8 files changed

+85
-107
lines changed

config/src/main/java/org/springframework/security/config/annotation/web/configurers/X509Configurer.java

Lines changed: 2 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,6 @@
3333
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
3434
import org.springframework.security.web.authentication.preauth.PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails;
3535
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
36-
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
3736
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
3837
import org.springframework.security.web.authentication.preauth.x509.X509PrincipalExtractor;
3938
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
@@ -75,7 +74,6 @@
7574
*
7675
* @author Rob Winch
7776
* @author Ngoc Nhan
78-
* @author Max Batischev
7977
* @since 3.2
8078
*/
8179
public final class X509Configurer<H extends HttpSecurityBuilder<H>>
@@ -163,38 +161,17 @@ public X509Configurer<H> authenticationUserDetailsService(
163161
* @param subjectPrincipalRegex the regex to extract the user principal from the
164162
* certificate (i.e. "CN=(.*?)(?:,|$)").
165163
* @return the {@link X509Configurer} for further customizations
166-
* @deprecated Please use {{@link #extractPrincipalNameFromEmail(boolean)}} instead
164+
* @deprecated Please use {{@link #x509PrincipalExtractor(X509PrincipalExtractor)}
165+
* instead
167166
*/
168167
@Deprecated
169168
public X509Configurer<H> subjectPrincipalRegex(String subjectPrincipalRegex) {
170-
if (this.x509PrincipalExtractor instanceof SubjectX500PrincipalExtractor) {
171-
throw new IllegalStateException(
172-
"Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. "
173-
+ "Please use one or the other.");
174-
}
175169
SubjectDnX509PrincipalExtractor principalExtractor = new SubjectDnX509PrincipalExtractor();
176170
principalExtractor.setSubjectDnRegex(subjectPrincipalRegex);
177171
this.x509PrincipalExtractor = principalExtractor;
178172
return this;
179173
}
180174

181-
/**
182-
* If true then DN will be extracted from EMAIlADDRESS, defaults to {@code false}
183-
* @param extractPrincipalNameFromEmail whether to extract DN from EMAIlADDRESS
184-
* @since 7.0
185-
*/
186-
public X509Configurer<H> extractPrincipalNameFromEmail(boolean extractPrincipalNameFromEmail) {
187-
if (this.x509PrincipalExtractor instanceof SubjectDnX509PrincipalExtractor) {
188-
throw new IllegalStateException(
189-
"Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. "
190-
+ "Please use one or the other.");
191-
}
192-
SubjectX500PrincipalExtractor extractor = new SubjectX500PrincipalExtractor();
193-
extractor.setExtractPrincipalNameFromEmail(extractPrincipalNameFromEmail);
194-
this.x509PrincipalExtractor = extractor;
195-
return this;
196-
}
197-
198175
@Override
199176
public void init(H http) {
200177
PreAuthenticatedAuthenticationProvider authenticationProvider = new PreAuthenticatedAuthenticationProvider();

config/src/main/java/org/springframework/security/config/http/AuthenticationConfigBuilder.java

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2025 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -57,7 +57,6 @@
5757
import org.springframework.security.web.authentication.preauth.j2ee.J2eeBasedPreAuthenticatedWebAuthenticationDetailsSource;
5858
import org.springframework.security.web.authentication.preauth.j2ee.J2eePreAuthenticatedProcessingFilter;
5959
import org.springframework.security.web.authentication.preauth.x509.SubjectDnX509PrincipalExtractor;
60-
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
6160
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
6261
import org.springframework.security.web.authentication.ui.DefaultLoginPageGeneratingFilter;
6362
import org.springframework.security.web.authentication.ui.DefaultLogoutPageGeneratingFilter;
@@ -523,25 +522,12 @@ void createX509Filter(BeanReference authManager,
523522
filterBuilder.addPropertyValue("securityContextHolderStrategy",
524523
authenticationFilterSecurityContextHolderStrategyRef);
525524
String regex = x509Elt.getAttribute("subject-principal-regex");
526-
String extractPrincipalNameFromEmail = x509Elt.getAttribute("extract-principal-name-from-email");
527-
if (StringUtils.hasText(regex) && StringUtils.hasText(extractPrincipalNameFromEmail)) {
528-
throw new IllegalStateException(
529-
"Cannot use subjectPrincipalRegex and extractPrincipalNameFromEmail together. "
530-
+ "Please use one or the other.");
531-
}
532525
if (StringUtils.hasText(regex)) {
533526
BeanDefinitionBuilder extractor = BeanDefinitionBuilder
534527
.rootBeanDefinition(SubjectDnX509PrincipalExtractor.class);
535528
extractor.addPropertyValue("subjectDnRegex", regex);
536529
filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition());
537530
}
538-
if (StringUtils.hasText(extractPrincipalNameFromEmail)) {
539-
BeanDefinitionBuilder extractor = BeanDefinitionBuilder
540-
.rootBeanDefinition(SubjectX500PrincipalExtractor.class);
541-
extractor.addPropertyValue("extractPrincipalNameFromEmail",
542-
Boolean.parseBoolean(extractPrincipalNameFromEmail));
543-
filterBuilder.addPropertyValue("principalExtractor", extractor.getBeanDefinition());
544-
}
545531
injectAuthenticationDetailsSource(x509Elt, filterBuilder);
546532
filter = (RootBeanDefinition) filterBuilder.getBeanDefinition();
547533
createPrauthEntryPoint(x509Elt);

config/src/main/kotlin/org/springframework/security/config/annotation/web/X509Dsl.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@ class X509Dsl {
5151
var authenticationDetailsSource: AuthenticationDetailsSource<HttpServletRequest, PreAuthenticatedGrantedAuthoritiesWebAuthenticationDetails>? = null
5252
var userDetailsService: UserDetailsService? = null
5353
var authenticationUserDetailsService: AuthenticationUserDetailsService<PreAuthenticatedAuthenticationToken>? = null
54+
@Deprecated("Use x509PrincipalExtractor instead")
5455
var subjectPrincipalRegex: String? = null
5556

5657

config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1053,9 +1053,6 @@ x509.attlist &=
10531053
x509.attlist &=
10541054
## Reference to an AuthenticationDetailsSource which will be used by the authentication filter
10551055
attribute authentication-details-source-ref {xsd:token}?
1056-
x509.attlist &=
1057-
## If true then DN will be extracted from EMAIlADDRESS
1058-
attribute extract-principal-name-from-email {xsd:token}?
10591056

10601057
jee =
10611058
## Adds a J2eePreAuthenticatedProcessingFilter to the filter chain to provide integration with container authentication.

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

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -2917,12 +2917,6 @@
29172917
</xs:documentation>
29182918
</xs:annotation>
29192919
</xs:attribute>
2920-
<xs:attribute name="extract-principal-name-from-email" type="xs:token">
2921-
<xs:annotation>
2922-
<xs:documentation>If true then DN will be extracted from EMAIlADDRESS
2923-
</xs:documentation>
2924-
</xs:annotation>
2925-
</xs:attribute>
29262920
</xs:attributeGroup>
29272921
<xs:element name="jee">
29282922
<xs:annotation>

config/src/test/java/org/springframework/security/config/annotation/web/configurers/X509ConfigurerTests.java

Lines changed: 81 additions & 38 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2025 the original author or authors.
2+
* Copyright 2002-2023 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -43,7 +43,9 @@
4343
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
4444
import org.springframework.security.web.SecurityFilterChain;
4545
import org.springframework.security.web.authentication.preauth.PreAuthenticatedAuthenticationToken;
46+
import org.springframework.security.web.authentication.preauth.x509.SubjectX500PrincipalExtractor;
4647
import org.springframework.security.web.authentication.preauth.x509.X509AuthenticationFilter;
48+
import org.springframework.security.web.authentication.preauth.x509.X509TestUtils;
4749
import org.springframework.test.web.servlet.MockMvc;
4850

4951
import static org.assertj.core.api.Assertions.assertThat;
@@ -123,16 +125,6 @@ public void x509WhenSubjectPrincipalRegexInLambdaThenUsesRegexToExtractPrincipal
123125
// @formatter:on
124126
}
125127

126-
@Test
127-
public void x509WhenExtractPrincipalNameFromEmailIsTrueThenUsesEmailAddressToExtractPrincipal() throws Exception {
128-
this.spring.register(EmailPrincipalConfig.class).autowire();
129-
X509Certificate certificate = loadCert("max.cer");
130-
// @formatter:off
131-
this.mvc.perform(get("/").with(x509(certificate)))
132-
.andExpect(authenticated().withUsername("[email protected]"));
133-
// @formatter:on
134-
}
135-
136128
@Test
137129
public void x509WhenUserDetailsServiceNotConfiguredThenUsesBean() throws Exception {
138130
this.spring.register(UserDetailsServiceBeanConfig.class).autowire();
@@ -165,6 +157,28 @@ public void x509WhenStatelessSessionManagementThenDoesNotCreateSession() throws
165157
// @formatter:on
166158
}
167159

160+
@Test
161+
public void x509WhenSubjectX500PrincipalExtractor() throws Exception {
162+
this.spring.register(SubjectX500PrincipalExtractorConfig.class).autowire();
163+
X509Certificate certificate = loadCert("rod.cer");
164+
// @formatter:off
165+
this.mvc.perform(get("/").with(x509(certificate)))
166+
.andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull())
167+
.andExpect(authenticated().withUsername("rod"));
168+
// @formatter:on
169+
}
170+
171+
@Test
172+
public void x509WhenSubjectX500PrincipalExtractorBean() throws Exception {
173+
this.spring.register(SubjectX500PrincipalExtractorEmailConfig.class).autowire();
174+
X509Certificate certificate = X509TestUtils.buildTestCertificate();
175+
// @formatter:off
176+
this.mvc.perform(get("/").with(x509(certificate)))
177+
.andExpect((result) -> assertThat(result.getRequest().getSession(false)).isNull())
178+
.andExpect(authenticated().withUsername("luke@monkeymachine"));
179+
// @formatter:on
180+
}
181+
168182
private <T extends Certificate> T loadCert(String location) {
169183
try (InputStream is = new ClassPathResource(location).getInputStream()) {
170184
CertificateFactory certFactory = CertificateFactory.getInstance("X.509");
@@ -287,33 +301,6 @@ UserDetailsService userDetailsService() {
287301

288302
}
289303

290-
@Configuration
291-
@EnableWebSecurity
292-
static class EmailPrincipalConfig {
293-
294-
@Bean
295-
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
296-
// @formatter:off
297-
http
298-
.x509((x509) ->
299-
x509.extractPrincipalNameFromEmail(true)
300-
);
301-
// @formatter:on
302-
return http.build();
303-
}
304-
305-
@Bean
306-
UserDetailsService userDetailsService() {
307-
UserDetails user = User.withDefaultPasswordEncoder()
308-
.username("[email protected]")
309-
.password("password")
310-
.roles("USER", "ADMIN")
311-
.build();
312-
return new InMemoryUserDetailsManager(user);
313-
}
314-
315-
}
316-
317304
@Configuration
318305
@EnableWebSecurity
319306
static class UserDetailsServiceBeanConfig {
@@ -397,4 +384,60 @@ UserDetailsService userDetailsService() {
397384

398385
}
399386

387+
@Configuration
388+
@EnableWebSecurity
389+
static class SubjectX500PrincipalExtractorConfig {
390+
391+
@Bean
392+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
393+
// @formatter:off
394+
http
395+
.x509((x509) -> x509
396+
.x509PrincipalExtractor(new SubjectX500PrincipalExtractor())
397+
);
398+
// @formatter:on
399+
return http.build();
400+
}
401+
402+
@Bean
403+
UserDetailsService userDetailsService() {
404+
UserDetails user = User.withDefaultPasswordEncoder()
405+
.username("rod")
406+
.password("password")
407+
.roles("USER", "ADMIN")
408+
.build();
409+
return new InMemoryUserDetailsManager(user);
410+
}
411+
412+
}
413+
414+
@Configuration
415+
@EnableWebSecurity
416+
static class SubjectX500PrincipalExtractorEmailConfig {
417+
418+
@Bean
419+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
420+
SubjectX500PrincipalExtractor principalExtractor = new SubjectX500PrincipalExtractor();
421+
principalExtractor.setExtractPrincipalNameFromEmail(true);
422+
// @formatter:off
423+
http
424+
.x509((x509) -> x509
425+
.x509PrincipalExtractor(principalExtractor)
426+
);
427+
// @formatter:on
428+
return http.build();
429+
}
430+
431+
@Bean
432+
UserDetailsService userDetailsService() {
433+
UserDetails user = User.withDefaultPasswordEncoder()
434+
.username("luke@monkeymachine")
435+
.password("password")
436+
.roles("USER", "ADMIN")
437+
.build();
438+
return new InMemoryUserDetailsManager(user);
439+
}
440+
441+
}
442+
400443
}

config/src/test/resources/max.cer

Lines changed: 0 additions & 17 deletions
This file was deleted.

docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -2229,9 +2229,6 @@ Defines a regular expression which will be used to extract the username from the
22292229
Allows a specific `UserDetailsService` to be used with X.509 in the case where multiple instances are configured.
22302230
If not set, an attempt will be made to locate a suitable instance automatically and use that.
22312231

2232-
[[nsa-x509-extract-principal-name-from-email]]
2233-
* **extract-principal-name-from-email**
2234-
If true then DN will be extracted from EMAIlADDRESS.
22352232

22362233
[[nsa-filter-chain-map]]
22372234
== <filter-chain-map>

0 commit comments

Comments
 (0)