Skip to content

Commit 4f860a5

Browse files
committed
Merge branch '6.4.x'
# Conflicts: # config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java
2 parents 474b5e1 + d97e01d commit 4f860a5

File tree

4 files changed

+199
-12
lines changed

4 files changed

+199
-12
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

+19-6
Original file line numberDiff line numberDiff line change
@@ -174,6 +174,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
174174

175175
private String loginProcessingUrl = OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
176176

177+
private ClientRegistrationRepository clientRegistrationRepository;
178+
179+
private OAuth2AuthorizedClientRepository authorizedClientRepository;
180+
177181
/**
178182
* Sets the repository of client registrations.
179183
* @param clientRegistrationRepository the repository of client registrations
@@ -183,6 +187,7 @@ public OAuth2LoginConfigurer<B> clientRegistrationRepository(
183187
ClientRegistrationRepository clientRegistrationRepository) {
184188
Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
185189
this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
190+
this.clientRegistrationRepository = clientRegistrationRepository;
186191
return this;
187192
}
188193

@@ -196,6 +201,7 @@ public OAuth2LoginConfigurer<B> authorizedClientRepository(
196201
OAuth2AuthorizedClientRepository authorizedClientRepository) {
197202
Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
198203
this.getBuilder().setSharedObject(OAuth2AuthorizedClientRepository.class, authorizedClientRepository);
204+
this.authorizedClientRepository = authorizedClientRepository;
199205
return this;
200206
}
201207

@@ -341,8 +347,7 @@ public OAuth2LoginConfigurer<B> userInfoEndpoint(Customizer<UserInfoEndpointConf
341347
@Override
342348
public void init(B http) throws Exception {
343349
OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
344-
OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
345-
OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
350+
this.getClientRegistrationRepository(), this.getAuthorizedClientRepository(), this.loginProcessingUrl);
346351
authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
347352
this.setAuthenticationFilter(authenticationFilter);
348353
super.loginProcessingUrl(this.loginProcessingUrl);
@@ -433,8 +438,7 @@ private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
433438
if (this.authorizationEndpointConfig.authorizationRequestResolver != null) {
434439
return this.authorizationEndpointConfig.authorizationRequestResolver;
435440
}
436-
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
437-
.getClientRegistrationRepository(getBuilder());
441+
ClientRegistrationRepository clientRegistrationRepository = this.getClientRegistrationRepository();
438442
ResolvableType resolvableType = ResolvableType.forClass(OAuth2AuthorizationRequestResolver.class);
439443
OAuth2AuthorizationRequestResolver bean = getBeanOrNull(resolvableType);
440444
if (bean != null) {
@@ -447,6 +451,16 @@ private OAuth2AuthorizationRequestResolver getAuthorizationRequestResolver() {
447451
return new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository, authorizationRequestBaseUri);
448452
}
449453

454+
private ClientRegistrationRepository getClientRegistrationRepository() {
455+
return (this.clientRegistrationRepository != null) ? this.clientRegistrationRepository
456+
: OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder());
457+
}
458+
459+
private OAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
460+
return (this.authorizedClientRepository != null) ? this.authorizedClientRepository
461+
: OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder());
462+
}
463+
450464
@SuppressWarnings("unchecked")
451465
private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
452466
ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);
@@ -533,8 +547,7 @@ private void initDefaultLoginFilter(B http) {
533547
@SuppressWarnings("unchecked")
534548
private Map<String, String> getLoginLinks() {
535549
Iterable<ClientRegistration> clientRegistrations = null;
536-
ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
537-
.getClientRegistrationRepository(this.getBuilder());
550+
ClientRegistrationRepository clientRegistrationRepository = this.getClientRegistrationRepository();
538551
ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
539552
if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
540553
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

+66
Original file line numberDiff line numberDiff line change
@@ -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;
@@ -682,6 +685,30 @@ public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() {
682685
.collect(Collectors.toList())).isEmpty();
683686
}
684687

688+
@Test
689+
public void oidcLoginWhenOAuth2ClientBeansConfiguredThenNotShared() throws Exception {
690+
this.spring.register(OAuth2LoginConfigWithOAuth2Client.class, JwtDecoderFactoryConfig.class).autowire();
691+
OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
692+
this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response);
693+
this.request.setParameter("code", "code123");
694+
this.request.setParameter("state", authorizationRequest.getState());
695+
this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
696+
Authentication authentication = this.securityContextRepository
697+
.loadContext(new HttpRequestResponseHolder(this.request, this.response))
698+
.getAuthentication();
699+
assertThat(authentication.getAuthorities()).hasSize(1);
700+
assertThat(authentication.getAuthorities()).first()
701+
.isInstanceOf(OidcUserAuthority.class)
702+
.hasToString("OIDC_USER");
703+
704+
// Ensure shared objects set for OAuth2 Client are not used
705+
ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
706+
.getBean(ClientRegistrationRepository.class);
707+
OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext()
708+
.getBean(OAuth2AuthorizedClientRepository.class);
709+
verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository);
710+
}
711+
685712
private void loadConfig(Class<?>... configs) {
686713
AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
687714
applicationContext.register(configs);
@@ -1241,6 +1268,45 @@ SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
12411268

12421269
}
12431270

1271+
@Configuration
1272+
@EnableWebSecurity
1273+
static class OAuth2LoginConfigWithOAuth2Client extends CommonLambdaSecurityFilterChainConfig {
1274+
1275+
private final ClientRegistrationRepository clientRegistrationRepository = mock(
1276+
ClientRegistrationRepository.class);
1277+
1278+
private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock(
1279+
OAuth2AuthorizedClientRepository.class);
1280+
1281+
@Bean
1282+
SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
1283+
// @formatter:off
1284+
http
1285+
.oauth2Login((oauth2Login) -> oauth2Login
1286+
.clientRegistrationRepository(
1287+
new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))
1288+
.authorizedClientRepository(new HttpSessionOAuth2AuthorizedClientRepository())
1289+
)
1290+
.oauth2Client((oauth2Client) -> oauth2Client
1291+
.clientRegistrationRepository(this.clientRegistrationRepository)
1292+
.authorizedClientRepository(this.authorizedClientRepository)
1293+
);
1294+
// @formatter:on
1295+
return super.configureFilterChain(http);
1296+
}
1297+
1298+
@Bean
1299+
ClientRegistrationRepository clientRegistrationRepository() {
1300+
return this.clientRegistrationRepository;
1301+
}
1302+
1303+
@Bean
1304+
OAuth2AuthorizedClientRepository authorizedClientRepository() {
1305+
return this.authorizedClientRepository;
1306+
}
1307+
1308+
}
1309+
12441310
private abstract static class CommonSecurityFilterChainConfig {
12451311

12461312
SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception {

0 commit comments

Comments
 (0)