Skip to content

Commit d97e01d

Browse files
committed
Merge branch '6.3.x' into 6.4.x
Closes gh-16466
2 parents bb8e757 + 211fa52 commit d97e01d

File tree

4 files changed

+201
-14
lines changed

4 files changed

+201
-14
lines changed

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

+22-5
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.
@@ -99,6 +99,10 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
9999

100100
private AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer = new AuthorizationCodeGrantConfigurer();
101101

102+
private ClientRegistrationRepository clientRegistrationRepository;
103+
104+
private OAuth2AuthorizedClientRepository authorizedClientRepository;
105+
102106
/**
103107
* Sets the repository of client registrations.
104108
* @param clientRegistrationRepository the repository of client registrations
@@ -108,6 +112,7 @@ public OAuth2ClientConfigurer<B> clientRegistrationRepository(
108112
ClientRegistrationRepository clientRegistrationRepository) {
109113
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
110114
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
115+
this.clientRegistrationRepository = clientRegistrationRepository;
111116
return this;
112117
}
113118

@@ -120,6 +125,7 @@ public OAuth2ClientConfigurer<B> authorizedClientRepository(
120125
OAuth2AuthorizedClientRepository authorizedClientRepository) {
121126
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
122127
this.getBuilder().setSharedObject(OAuth2AuthorizedClientRepository.class, authorizedClientRepository);
128+
this.authorizedClientRepository = authorizedClientRepository;
123129
return this;
124130
}
125131

@@ -284,8 +290,7 @@ private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
284290
if (this.authorizationRequestResolver != null) {
285291
return this.authorizationRequestResolver;
286292
}
287-
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
288-
.getClientRegistrationRepository(getBuilder());
293+
ClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository(getBuilder());
289294
ResolvableType resolvableType = ResolvableType.forClass(OAuth2AuthorizationRequestResolver.class);
290295
OAuth2AuthorizationRequestResolver bean = getBeanOrNull(resolvableType);
291296
return (bean != null) ? bean : new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
@@ -295,8 +300,8 @@ private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
295300
private OAuth2AuthorizationCodeGrantFilter createAuthorizationCodeGrantFilter(B builder) {
296301
AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
297302
OAuth2AuthorizationCodeGrantFilter authorizationCodeGrantFilter = new OAuth2AuthorizationCodeGrantFilter(
298-
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder),
299-
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(builder), authenticationManager);
303+
getClientRegistrationRepository(builder), getAuthorizedClientRepository(builder),
304+
authenticationManager);
300305
if (this.authorizationRequestRepository != null) {
301306
authorizationCodeGrantFilter.setAuthorizationRequestRepository(this.authorizationRequestRepository);
302307
}
@@ -318,6 +323,18 @@ private OAuth2AccessTokenResponseClient<OAuth2AuthorizationCodeGrantRequest> get
318323
return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
319324
}
320325

326+
private ClientRegistrationRepository getClientRegistrationRepository(B builder) {
327+
return (OAuth2ClientConfigurer.this.clientRegistrationRepository != null)
328+
? OAuth2ClientConfigurer.this.clientRegistrationRepository
329+
: OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder);
330+
}
331+
332+
private OAuth2AuthorizedClientRepository getAuthorizedClientRepository(B builder) {
333+
return (OAuth2ClientConfigurer.this.authorizedClientRepository != null)
334+
? OAuth2ClientConfigurer.this.authorizedClientRepository
335+
: OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(builder);
336+
}
337+
321338
@SuppressWarnings("unchecked")
322339
private <T> T getBeanOrNull(ResolvableType type) {
323340
ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);

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

+20-7
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.
@@ -173,6 +173,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
173173

174174
private String loginProcessingUrl = OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
175175

176+
private ClientRegistrationRepository clientRegistrationRepository;
177+
178+
private OAuth2AuthorizedClientRepository authorizedClientRepository;
179+
176180
/**
177181
* Sets the repository of client registrations.
178182
* @param clientRegistrationRepository the repository of client registrations
@@ -182,6 +186,7 @@ public OAuth2LoginConfigurer<B> clientRegistrationRepository(
182186
ClientRegistrationRepository clientRegistrationRepository) {
183187
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
184188
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
189+
this.clientRegistrationRepository = clientRegistrationRepository;
185190
return this;
186191
}
187192

@@ -195,6 +200,7 @@ public OAuth2LoginConfigurer<B> authorizedClientRepository(
195200
OAuth2AuthorizedClientRepository authorizedClientRepository) {
196201
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
197202
this.getBuilder().setSharedObject(OAuth2AuthorizedClientRepository.class, authorizedClientRepository);
203+
this.authorizedClientRepository = authorizedClientRepository;
198204
return this;
199205
}
200206

@@ -340,8 +346,7 @@ public OAuth2LoginConfigurer<B> userInfoEndpoint(Customizer<UserInfoEndpointConf
340346
@Override
341347
public void init(B http) throws Exception {
342348
OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
343-
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
344-
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
349+
this.getClientRegistrationRepository(), this.getAuthorizedClientRepository(), this.loginProcessingUrl);
345350
authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
346351
this.setAuthenticationFilter(authenticationFilter);
347352
super.loginProcessingUrl(this.loginProcessingUrl);
@@ -407,8 +412,7 @@ public void configure(B http) throws Exception {
407412
authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
408413
}
409414
authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
410-
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
411-
authorizationRequestBaseUri);
415+
this.getClientRegistrationRepository(), authorizationRequestBaseUri);
412416
}
413417
if (this.authorizationEndpointConfig.authorizationRequestRepository != null) {
414418
authorizationRequestFilter
@@ -440,6 +444,16 @@ protected RequestMatcher createLoginProcessingUrlMatcher(String loginProcessingU
440444
return new AntPathRequestMatcher(loginProcessingUrl);
441445
}
442446

447+
private ClientRegistrationRepository getClientRegistrationRepository() {
448+
return (this.clientRegistrationRepository != null) ? this.clientRegistrationRepository
449+
: OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder());
450+
}
451+
452+
private OAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
453+
return (this.authorizedClientRepository != null) ? this.authorizedClientRepository
454+
: OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder());
455+
}
456+
443457
@SuppressWarnings("unchecked")
444458
private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
445459
ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);
@@ -526,8 +540,7 @@ private void initDefaultLoginFilter(B http) {
526540
@SuppressWarnings("unchecked")
527541
private Map<String, String> getLoginLinks() {
528542
Iterable<ClientRegistration> clientRegistrations = null;
529-
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
530-
.getClientRegistrationRepository(this.getBuilder());
543+
ClientRegistrationRepository clientRegistrationRepository = this.getClientRegistrationRepository();
531544
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
532545
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
533546
clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;

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

+92-1
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.
@@ -75,6 +75,7 @@
7575
import static org.mockito.BDDMockito.given;
7676
import static org.mockito.Mockito.mock;
7777
import static org.mockito.Mockito.verify;
78+
import static org.mockito.Mockito.verifyNoInteractions;
7879
import static org.springframework.security.config.Customizer.withDefaults;
7980
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
8081
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -301,6 +302,49 @@ public void configureWhenCustomAuthorizationRequestResolverBeanPresentThenAuthor
301302
verify(authorizationRequestResolver).resolve(any());
302303
}
303304

305+
@Test
306+
public void configureWhenOAuth2LoginBeansConfiguredThenNotShared() throws Exception {
307+
this.spring.register(OAuth2ClientConfigWithOAuth2Login.class).autowire();
308+
// Setup the Authorization Request in the session
309+
Map<String, Object> attributes = new HashMap<>();
310+
attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId());
311+
// @formatter:off
312+
OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
313+
.authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri())
314+
.clientId(this.registration1.getClientId())
315+
.redirectUri("http://localhost/client-1")
316+
.state("state")
317+
.attributes(attributes)
318+
.build();
319+
// @formatter:on
320+
AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
321+
MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
322+
MockHttpServletResponse response = new MockHttpServletResponse();
323+
authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
324+
MockHttpSession session = (MockHttpSession) request.getSession();
325+
String principalName = "user1";
326+
TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password");
327+
// @formatter:off
328+
MockHttpServletRequestBuilder clientRequest = get("/client-1")
329+
.param(OAuth2ParameterNames.CODE, "code")
330+
.param(OAuth2ParameterNames.STATE, "state")
331+
.with(authentication(authentication))
332+
.session(session);
333+
this.mockMvc.perform(clientRequest)
334+
.andExpect(status().is3xxRedirection())
335+
.andExpect(redirectedUrl("http://localhost/client-1"));
336+
// @formatter:on
337+
OAuth2AuthorizedClient authorizedClient = authorizedClientRepository
338+
.loadAuthorizedClient(this.registration1.getRegistrationId(), authentication, request);
339+
assertThat(authorizedClient).isNotNull();
340+
// Ensure shared objects set for OAuth2 Client are not used
341+
ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
342+
.getBean(ClientRegistrationRepository.class);
343+
OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext()
344+
.getBean(OAuth2AuthorizedClientRepository.class);
345+
verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository);
346+
}
347+
304348
@EnableWebSecurity
305349
@Configuration
306350
@EnableWebMvc
@@ -388,4 +432,51 @@ OAuth2AuthorizationRequestResolver authorizationRequestResolver() {
388432

389433
}
390434

435+
@Configuration
436+
@EnableWebSecurity
437+
@EnableWebMvc
438+
static class OAuth2ClientConfigWithOAuth2Login {
439+
440+
private final ClientRegistrationRepository clientRegistrationRepository = mock(
441+
ClientRegistrationRepository.class);
442+
443+
private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock(
444+
OAuth2AuthorizedClientRepository.class);
445+
446+
@Bean
447+
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
448+
// @formatter:off
449+
http
450+
.authorizeHttpRequests((authorize) -> authorize
451+
.anyRequest().authenticated()
452+
)
453+
.oauth2Client((oauth2Client) -> oauth2Client
454+
.clientRegistrationRepository(OAuth2ClientConfigurerTests.clientRegistrationRepository)
455+
.authorizedClientService(OAuth2ClientConfigurerTests.authorizedClientService)
456+
.authorizationCodeGrant((authorizationCode) -> authorizationCode
457+
.authorizationRequestResolver(authorizationRequestResolver)
458+
.authorizationRedirectStrategy(authorizationRedirectStrategy)
459+
.accessTokenResponseClient(accessTokenResponseClient)
460+
)
461+
)
462+
.oauth2Login((oauth2Login) -> oauth2Login
463+
.clientRegistrationRepository(this.clientRegistrationRepository)
464+
.authorizedClientRepository(this.authorizedClientRepository)
465+
);
466+
// @formatter:on
467+
return http.build();
468+
}
469+
470+
@Bean
471+
ClientRegistrationRepository clientRegistrationRepository() {
472+
return this.clientRegistrationRepository;
473+
}
474+
475+
@Bean
476+
OAuth2AuthorizedClientRepository authorizedClientRepository() {
477+
return this.authorizedClientRepository;
478+
}
479+
480+
}
481+
391482
}

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

+67-1
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.
@@ -73,7 +73,9 @@
7373
import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
7474
import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
7575
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
76+
import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
7677
import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
78+
import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
7779
import org.springframework.security.oauth2.core.OAuth2AccessToken;
7880
import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
7981
import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -115,6 +117,7 @@
115117
import static org.mockito.Mockito.atLeastOnce;
116118
import static org.mockito.Mockito.mock;
117119
import static org.mockito.Mockito.verify;
120+
import static org.mockito.Mockito.verifyNoInteractions;
118121
import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
119122
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
120123
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -669,6 +672,30 @@ public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() {
669672
.collect(Collectors.toList())).isEmpty();
670673
}
671674

675+
@Test
676+
public void oidcLoginWhenOAuth2ClientBeansConfiguredThenNotShared() throws Exception {
677+
this.spring.register(OAuth2LoginConfigWithOAuth2Client.class, JwtDecoderFactoryConfig.class).autowire();
678+
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
679+
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response);
680+
this.request.setParameter("code", "code123");
681+
this.request.setParameter("state", authorizationRequest.getState());
682+
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
683+
Authentication authentication = this.securityContextRepository
684+
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
685+
.getAuthentication();
686+
assertThat(authentication.getAuthorities()).hasSize(1);
687+
assertThat(authentication.getAuthorities()).first()
688+
.isInstanceOf(OidcUserAuthority.class)
689+
.hasToString("OIDC_USER");
690+
691+
// Ensure shared objects set for OAuth2 Client are not used
692+
ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
693+
.getBean(ClientRegistrationRepository.class);
694+
OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext()
695+
.getBean(OAuth2AuthorizedClientRepository.class);
696+
verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository);
697+
}
698+
672699
private void loadConfig(Class<?>... configs) {
673700
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
674701
applicationContext.register(configs);
@@ -1192,6 +1219,45 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
11921219

11931220
}
11941221

1222+
@Configuration
1223+
@EnableWebSecurity
1224+
static class OAuth2LoginConfigWithOAuth2Client extends CommonLambdaSecurityFilterChainConfig {
1225+
1226+
private final ClientRegistrationRepository clientRegistrationRepository = mock(
1227+
ClientRegistrationRepository.class);
1228+
1229+
private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock(
1230+
OAuth2AuthorizedClientRepository.class);
1231+
1232+
@Bean
1233+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
1234+
// @formatter:off
1235+
http
1236+
.oauth2Login((oauth2Login) -> oauth2Login
1237+
.clientRegistrationRepository(
1238+
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))
1239+
.authorizedClientRepository(new HttpSessionOAuth2AuthorizedClientRepository())
1240+
)
1241+
.oauth2Client((oauth2Client) -> oauth2Client
1242+
.clientRegistrationRepository(this.clientRegistrationRepository)
1243+
.authorizedClientRepository(this.authorizedClientRepository)
1244+
);
1245+
// @formatter:on
1246+
return super.configureFilterChain(http);
1247+
}
1248+
1249+
@Bean
1250+
ClientRegistrationRepository clientRegistrationRepository() {
1251+
return this.clientRegistrationRepository;
1252+
}
1253+
1254+
@Bean
1255+
OAuth2AuthorizedClientRepository authorizedClientRepository() {
1256+
return this.authorizedClientRepository;
1257+
}
1258+
1259+
}
1260+
11951261
private abstract static class CommonSecurityFilterChainConfig {
11961262

11971263
SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception {

0 commit comments

Comments
 (0)