Skip to content

Commit d9f35a9

Browse files
authored
Merge branch 'spring-projects:main' into main
2 parents 895a03e + bf9b95a commit d9f35a9

File tree

12 files changed

+152
-73
lines changed

12 files changed

+152
-73
lines changed

.github/dco.yml

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
require:
2+
members: false

CONTRIBUTING.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -90,8 +90,8 @@ Please do your best to follow these steps.
9090
Don't worry if you don't get them all correct the first time, we will help you.
9191

9292
[[sign-cla]]
93-
1. If you have not previously done so, please sign the https://cla.spring.io/sign/spring[Contributor License Agreement].
94-
You will be reminded automatically when you submit the PR.
93+
1. All commits must include a __Signed-off-by__ trailer at the end of each commit message to indicate that the contributor agrees to the Developer Certificate of Origin.
94+
For additional details, please refer to the blog post https://spring.io/blog/2025/01/06/hello-dco-goodbye-cla-simplifying-contributions-to-spring[Hello DCO, Goodbye CLA: Simplifying Contributions to Spring].
9595
[[create-an-issue]]
9696
1. Must you https://github.com/spring-projects/spring-security/issues/new/choose[create an issue] first? No, but it is recommended for features and larger bug fixes. It's easier discuss with the team first to determine the right fix or enhancement.
9797
For typos and straightforward bug fixes, starting with a pull request is encouraged.

config/src/main/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfiguration.java

Lines changed: 9 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -22,7 +22,7 @@
2222

2323
import jakarta.servlet.Filter;
2424

25-
import org.springframework.beans.factory.BeanClassLoaderAware;
25+
import org.springframework.beans.factory.ObjectProvider;
2626
import org.springframework.beans.factory.annotation.Autowired;
2727
import org.springframework.beans.factory.config.BeanFactoryPostProcessor;
2828
import org.springframework.beans.factory.config.ConfigurableListableBeanFactory;
@@ -65,23 +65,16 @@
6565
* @see WebSecurity
6666
*/
6767
@Configuration(proxyBeanMethods = false)
68-
public class WebSecurityConfiguration implements ImportAware, BeanClassLoaderAware {
68+
public class WebSecurityConfiguration implements ImportAware {
6969

7070
private WebSecurity webSecurity;
7171

7272
private Boolean debugEnabled;
7373

74-
private List<SecurityConfigurer<Filter, WebSecurity>> webSecurityConfigurers;
75-
7674
private List<SecurityFilterChain> securityFilterChains = Collections.emptyList();
7775

7876
private List<WebSecurityCustomizer> webSecurityCustomizers = Collections.emptyList();
7977

80-
private ClassLoader beanClassLoader;
81-
82-
@Autowired(required = false)
83-
private HttpSecurity httpSecurity;
84-
8578
@Bean
8679
public static DelegatingApplicationListener delegatingApplicationListener() {
8780
return new DelegatingApplicationListener();
@@ -99,14 +92,15 @@ public SecurityExpressionHandler<FilterInvocation> webSecurityExpressionHandler(
9992
* @throws Exception
10093
*/
10194
@Bean(name = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME)
102-
public Filter springSecurityFilterChain() throws Exception {
95+
public Filter springSecurityFilterChain(ObjectProvider<HttpSecurity> provider) throws Exception {
10396
boolean hasFilterChain = !this.securityFilterChains.isEmpty();
10497
if (!hasFilterChain) {
10598
this.webSecurity.addSecurityFilterChainBuilder(() -> {
106-
this.httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
107-
this.httpSecurity.formLogin(Customizer.withDefaults());
108-
this.httpSecurity.httpBasic(Customizer.withDefaults());
109-
return this.httpSecurity.build();
99+
HttpSecurity httpSecurity = provider.getObject();
100+
httpSecurity.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated());
101+
httpSecurity.formLogin(Customizer.withDefaults());
102+
httpSecurity.httpBasic(Customizer.withDefaults());
103+
return httpSecurity.build();
110104
});
111105
}
112106
for (SecurityFilterChain securityFilterChain : this.securityFilterChains) {
@@ -164,7 +158,6 @@ public void setFilterChainProxySecurityConfigurer(ObjectPostProcessor<Object> ob
164158
for (SecurityConfigurer<Filter, WebSecurity> webSecurityConfigurer : webSecurityConfigurers) {
165159
this.webSecurity.apply(webSecurityConfigurer);
166160
}
167-
this.webSecurityConfigurers = webSecurityConfigurers;
168161
}
169162

170163
@Autowired(required = false)
@@ -193,11 +186,6 @@ public void setImportMetadata(AnnotationMetadata importMetadata) {
193186
}
194187
}
195188

196-
@Override
197-
public void setBeanClassLoader(ClassLoader classLoader) {
198-
this.beanClassLoader = classLoader;
199-
}
200-
201189
/**
202190
* A custom version of the Spring provided AnnotationAwareOrderComparator that uses
203191
* {@link AnnotationUtils#findAnnotation(Class, Class)} to look on super class

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

Lines changed: 4 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -31,6 +31,7 @@
3131
* {@link HttpSecurity}.
3232
*
3333
* @author Rob Winch
34+
* @author Ding Hao
3435
*/
3536
public abstract class AbstractHttpConfigurer<T extends AbstractHttpConfigurer<T, B>, B extends HttpSecurityBuilder<B>>
3637
extends SecurityConfigurerAdapter<DefaultSecurityFilterChain, B> {
@@ -70,13 +71,8 @@ protected SecurityContextHolderStrategy getSecurityContextHolderStrategy() {
7071
return this.securityContextHolderStrategy;
7172
}
7273
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);
73-
String[] names = context.getBeanNamesForType(SecurityContextHolderStrategy.class);
74-
if (names.length == 1) {
75-
this.securityContextHolderStrategy = context.getBean(SecurityContextHolderStrategy.class);
76-
}
77-
else {
78-
this.securityContextHolderStrategy = SecurityContextHolder.getContextHolderStrategy();
79-
}
74+
this.securityContextHolderStrategy = context.getBeanProvider(SecurityContextHolderStrategy.class)
75+
.getIfUnique(SecurityContextHolder::getContextHolderStrategy);
8076
return this.securityContextHolderStrategy;
8177
}
8278

config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

Lines changed: 22 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -68,6 +68,7 @@
6868
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
6969
import org.springframework.security.oauth2.client.web.AuthenticatedPrincipalOAuth2AuthorizedClientRepository;
7070
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
71+
import org.springframework.security.oauth2.client.web.DefaultOAuth2AuthorizationRequestResolver;
7172
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestRedirectFilter;
7273
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
7374
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
@@ -396,20 +397,8 @@ public void init(B http) throws Exception {
396397

397398
@Override
398399
public void configure(B http) throws Exception {
399-
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter;
400-
if (this.authorizationEndpointConfig.authorizationRequestResolver != null) {
401-
authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
402-
this.authorizationEndpointConfig.authorizationRequestResolver);
403-
}
404-
else {
405-
String authorizationRequestBaseUri = this.authorizationEndpointConfig.authorizationRequestBaseUri;
406-
if (authorizationRequestBaseUri == null) {
407-
authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
408-
}
409-
authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
410-
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
411-
authorizationRequestBaseUri);
412-
}
400+
OAuth2AuthorizationRequestRedirectFilter authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
401+
getAuthorizationRequestResolver());
413402
if (this.authorizationEndpointConfig.authorizationRequestRepository != null) {
414403
authorizationRequestFilter
415404
.setAuthorizationRequestRepository(this.authorizationEndpointConfig.authorizationRequestRepository);
@@ -440,6 +429,24 @@ protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingU
440429
return new AntPathRequestMatcher(loginProcessingUrl);
441430
}
442431

432+
private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
433+
if (this.authorizationEndpointConfig.authorizationRequestResolver != null) {
434+
return this.authorizationEndpointConfig.authorizationRequestResolver;
435+
}
436+
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
437+
.getClientRegistrationRepository(getBuilder());
438+
ResolvableType resolvableType = ResolvableType.forClass(OAuth2AuthorizationRequestResolver.class);
439+
OAuth2AuthorizationRequestResolver bean = getBeanOrNull(resolvableType);
440+
if (bean != null) {
441+
return bean;
442+
}
443+
String authorizationRequestBaseUri = this.authorizationEndpointConfig.authorizationRequestBaseUri;
444+
if (authorizationRequestBaseUri == null) {
445+
authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
446+
}
447+
return new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, authorizationRequestBaseUri);
448+
}
449+
443450
@SuppressWarnings("unchecked")
444451
private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
445452
ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);

config/src/test/java/org/springframework/security/config/annotation/web/configuration/WebSecurityConfigurationTests.java

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -27,8 +27,10 @@
2727
import org.junit.jupiter.api.Test;
2828
import org.junit.jupiter.api.extension.ExtendWith;
2929

30+
import org.springframework.beans.BeansException;
3031
import org.springframework.beans.factory.BeanCreationException;
3132
import org.springframework.beans.factory.annotation.Autowired;
33+
import org.springframework.beans.factory.config.BeanPostProcessor;
3234
import org.springframework.context.annotation.Bean;
3335
import org.springframework.context.annotation.Configuration;
3436
import org.springframework.context.annotation.Import;
@@ -326,6 +328,13 @@ public void loadConfigWhenTwoSecurityFilterChainsPresentAndSecondWithAnyRequestT
326328
.isInstanceOf(IllegalArgumentException.class);
327329
}
328330

331+
@Test
332+
public void avoidUnnecessaryHttpSecurityInstantiationWhenProvideOneSecurityFilterChain() {
333+
this.spring.register(SecurityFilterChainConfig.class).autowire();
334+
assertThat(this.spring.getContext().getBean(CountHttpSecurityBeanPostProcessor.class).instantiationCount)
335+
.isEqualTo(1);
336+
}
337+
329338
private void assertAnotherUserPermission(WebInvocationPrivilegeEvaluator privilegeEvaluator) {
330339
Authentication anotherUser = new TestingAuthenticationToken("anotherUser", "password", "ROLE_ANOTHER");
331340
assertThat(privilegeEvaluator.isAllowed("/user", anotherUser)).isFalse();
@@ -347,6 +356,32 @@ private void assertUserPermissions(WebInvocationPrivilegeEvaluator privilegeEval
347356
assertThat(privilegeEvaluator.isAllowed("/another", user)).isTrue();
348357
}
349358

359+
@Configuration
360+
@EnableWebSecurity
361+
@Import(CountHttpSecurityBeanPostProcessor.class)
362+
static class SecurityFilterChainConfig {
363+
364+
@Bean
365+
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
366+
return http.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated()).build();
367+
}
368+
369+
}
370+
371+
static class CountHttpSecurityBeanPostProcessor implements BeanPostProcessor {
372+
373+
int instantiationCount = 0;
374+
375+
@Override
376+
public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
377+
if (bean instanceof HttpSecurity) {
378+
this.instantiationCount++;
379+
}
380+
return bean;
381+
}
382+
383+
}
384+
350385
@Configuration
351386
@EnableWebSecurity
352387
@Import(AuthenticationTestConfiguration.class)

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java

Lines changed: 50 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2024 the original author or authors.
2+
* Copyright 2002-2025 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.
@@ -379,6 +379,19 @@ public void oauth2LoginWithCustomAuthorizationRequestParameters() throws Excepti
379379
"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1");
380380
}
381381

382+
@Test
383+
public void oauth2LoginWithCustomAuthorizationRequestParametersAndResolverAsBean() throws Exception {
384+
loadConfig(OAuth2LoginConfigCustomAuthorizationRequestResolverBean.class);
385+
// @formatter:off
386+
// @formatter:on
387+
String requestUri = "/oauth2/authorization/google";
388+
this.request = new MockHttpServletRequest("GET", requestUri);
389+
this.request.setServletPath(requestUri);
390+
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
391+
assertThat(this.response.getRedirectedUrl()).isEqualTo(
392+
"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1");
393+
}
394+
382395
@Test
383396
public void requestWhenOauth2LoginWithCustomAuthorizationRequestParametersThenParametersInRedirectedUrl()
384397
throws Exception {
@@ -940,6 +953,42 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
940953

941954
}
942955

956+
@Configuration
957+
@EnableWebSecurity
958+
static class OAuth2LoginConfigCustomAuthorizationRequestResolverBean extends CommonSecurityFilterChainConfig {
959+
960+
private ClientRegistrationRepository clientRegistrationRepository = new InMemoryClientRegistrationRepository(
961+
GOOGLE_CLIENT_REGISTRATION);
962+
963+
@Bean
964+
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
965+
// @formatter:off
966+
http
967+
.oauth2Login()
968+
.clientRegistrationRepository(this.clientRegistrationRepository)
969+
.authorizationEndpoint();
970+
// @formatter:on
971+
return super.configureFilterChain(http);
972+
}
973+
974+
@Bean
975+
OAuth2AuthorizationRequestResolver resolver() {
976+
OAuth2AuthorizationRequestResolver resolver = mock(OAuth2AuthorizationRequestResolver.class);
977+
// @formatter:off
978+
OAuth2AuthorizationRequest result = OAuth2AuthorizationRequest.authorizationCode()
979+
.authorizationUri("https://accounts.google.com/authorize")
980+
.clientId("client-id")
981+
.state("adsfa")
982+
.authorizationRequestUri(
983+
"https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=clientId&scope=openid+profile+email&state=state&redirect_uri=http%3A%2F%2Flocalhost%2Flogin%2Foauth2%2Fcode%2Fgoogle&custom-param1=custom-value1")
984+
.build();
985+
given(resolver.resolve(any())).willReturn(result);
986+
// @formatter:on
987+
return resolver;
988+
}
989+
990+
}
991+
943992
@Configuration
944993
@EnableWebSecurity
945994
static class OAuth2LoginConfigCustomAuthorizationRequestResolverInLambda

docs/modules/ROOT/pages/servlet/architecture.adoc

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -170,7 +170,7 @@ In fact, a `SecurityFilterChain` might have zero security `Filter` instances if
170170

171171
The Security Filters are inserted into the <<servlet-filterchainproxy>> with the <<servlet-securityfilterchain>> API.
172172
Those filters can be used for a number of different purposes, like
173-
xref:servlet/exploits/index.adoc[exploit protection],xref:servlet/authentication/index.adoc[authentication], xref:servlet/authorization/index.adoc[authorization], and more.
173+
xref:servlet/exploits/index.adoc[exploit protection], xref:servlet/authentication/index.adoc[authentication], xref:servlet/authorization/index.adoc[authorization], and more.
174174
The filters are executed in a specific order to guarantee that they are invoked at the right time, for example, the `Filter` that performs authentication should be invoked before the `Filter` that performs authorization.
175175
It is typically not necessary to know the ordering of Spring Security's ``Filter``s.
176176
However, there are times that it is beneficial to know the ordering, if you want to know them, you can check the {gh-url}/config/src/main/java/org/springframework/security/config/annotation/web/builders/FilterOrderRegistration.java[`FilterOrderRegistration` code].
@@ -609,7 +609,7 @@ try {
609609
}
610610
----
611611
<1> As described in <<servlet-filters-review>>, invoking `FilterChain.doFilter(request, response)` is the equivalent of invoking the rest of the application.
612-
This means that if another part of the application, (<<servlet-authorization-filtersecurityinterceptor,`FilterSecurityInterceptor`>> or method security) throws an `AuthenticationException` or `AccessDeniedException` it is caught and handled here.
612+
This means that if another part of the application, (xref:servlet/authorization/authorize-http-requests.adoc[`AuthorizationFilter`] or method security) throws an `AuthenticationException` or `AccessDeniedException` it is caught and handled here.
613613
<2> If the user is not authenticated or it is an `AuthenticationException`, __Start Authentication__.
614614
<3> Otherwise, __Access Denied__
615615

docs/modules/ROOT/pages/servlet/authentication/logout.adoc

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -226,7 +226,7 @@ Kotlin::
226226
----
227227
http {
228228
logout {
229-
deleteCookies = "our-custom-cookie"
229+
deleteCookies("our-custom-cookie")
230230
}
231231
}
232232
----

0 commit comments

Comments
 (0)