2
0
Эх сурвалжийг харах

Merge branch '6.3.x' into 6.4.x

Closes gh-16466
Steve Riesenberg 7 сар өмнө
parent
commit
d97e01d1de

+ 22 - 5
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -99,6 +99,10 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
 
 	private AuthorizationCodeGrantConfigurer authorizationCodeGrantConfigurer = new AuthorizationCodeGrantConfigurer();
 
+	private ClientRegistrationRepository clientRegistrationRepository;
+
+	private OAuth2AuthorizedClientRepository authorizedClientRepository;
+
 	/**
 	 * Sets the repository of client registrations.
 	 * @param clientRegistrationRepository the repository of client registrations
@@ -108,6 +112,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
 			ClientRegistrationRepository clientRegistrationRepository) {
 		Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
 		this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
+		this.clientRegistrationRepository = clientRegistrationRepository;
 		return this;
 	}
 
@@ -120,6 +125,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
 			OAuth2AuthorizedClientRepository authorizedClientRepository) {
 		Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
 		this.getBuilder().setSharedObject(OAuth2AuthorizedClientRepository.class, authorizedClientRepository);
+		this.authorizedClientRepository = authorizedClientRepository;
 		return this;
 	}
 
@@ -284,8 +290,7 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
 			if (this.authorizationRequestResolver != null) {
 				return this.authorizationRequestResolver;
 			}
-			ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
-				.getClientRegistrationRepository(getBuilder());
+			ClientRegistrationRepository clientRegistrationRepository = getClientRegistrationRepository(getBuilder());
 			ResolvableType resolvableType = ResolvableType.forClass(OAuth2AuthorizationRequestResolver.class);
 			OAuth2AuthorizationRequestResolver bean = getBeanOrNull(resolvableType);
 			return (bean != null) ? bean : new DefaultOAuth2AuthorizationRequestResolver(clientRegistrationRepository,
@@ -295,8 +300,8 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
 		private OAuth2AuthorizationCodeGrantFilter createAuthorizationCodeGrantFilter(B builder) {
 			AuthenticationManager authenticationManager = builder.getSharedObject(AuthenticationManager.class);
 			OAuth2AuthorizationCodeGrantFilter authorizationCodeGrantFilter = new OAuth2AuthorizationCodeGrantFilter(
-					OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder),
-					OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(builder), authenticationManager);
+					getClientRegistrationRepository(builder), getAuthorizedClientRepository(builder),
+					authenticationManager);
 			if (this.authorizationRequestRepository != null) {
 				authorizationCodeGrantFilter.setAuthorizationRequestRepository(this.authorizationRequestRepository);
 			}
@@ -318,6 +323,18 @@ public final class OAuth2ClientConfigurer<B extends HttpSecurityBuilder<B>>
 			return (bean != null) ? bean : new DefaultAuthorizationCodeTokenResponseClient();
 		}
 
+		private ClientRegistrationRepository getClientRegistrationRepository(B builder) {
+			return (OAuth2ClientConfigurer.this.clientRegistrationRepository != null)
+					? OAuth2ClientConfigurer.this.clientRegistrationRepository
+					: OAuth2ClientConfigurerUtils.getClientRegistrationRepository(builder);
+		}
+
+		private OAuth2AuthorizedClientRepository getAuthorizedClientRepository(B builder) {
+			return (OAuth2ClientConfigurer.this.authorizedClientRepository != null)
+					? OAuth2ClientConfigurer.this.authorizedClientRepository
+					: OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(builder);
+		}
+
 		@SuppressWarnings("unchecked")
 		private <T> T getBeanOrNull(ResolvableType type) {
 			ApplicationContext context = getBuilder().getSharedObject(ApplicationContext.class);

+ 20 - 7
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -173,6 +173,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 
 	private String loginProcessingUrl = OAuth2LoginAuthenticationFilter.DEFAULT_FILTER_PROCESSES_URI;
 
+	private ClientRegistrationRepository clientRegistrationRepository;
+
+	private OAuth2AuthorizedClientRepository authorizedClientRepository;
+
 	/**
 	 * Sets the repository of client registrations.
 	 * @param clientRegistrationRepository the repository of client registrations
@@ -182,6 +186,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 			ClientRegistrationRepository clientRegistrationRepository) {
 		Assert.notNull(clientRegistrationRepository, "clientRegistrationRepository cannot be null");
 		this.getBuilder().setSharedObject(ClientRegistrationRepository.class, clientRegistrationRepository);
+		this.clientRegistrationRepository = clientRegistrationRepository;
 		return this;
 	}
 
@@ -195,6 +200,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 			OAuth2AuthorizedClientRepository authorizedClientRepository) {
 		Assert.notNull(authorizedClientRepository, "authorizedClientRepository cannot be null");
 		this.getBuilder().setSharedObject(OAuth2AuthorizedClientRepository.class, authorizedClientRepository);
+		this.authorizedClientRepository = authorizedClientRepository;
 		return this;
 	}
 
@@ -340,8 +346,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	@Override
 	public void init(B http) throws Exception {
 		OAuth2LoginAuthenticationFilter authenticationFilter = new OAuth2LoginAuthenticationFilter(
-				OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
-				OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder()), this.loginProcessingUrl);
+				this.getClientRegistrationRepository(), this.getAuthorizedClientRepository(), this.loginProcessingUrl);
 		authenticationFilter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
 		this.setAuthenticationFilter(authenticationFilter);
 		super.loginProcessingUrl(this.loginProcessingUrl);
@@ -407,8 +412,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 				authorizationRequestBaseUri = OAuth2AuthorizationRequestRedirectFilter.DEFAULT_AUTHORIZATION_REQUEST_BASE_URI;
 			}
 			authorizationRequestFilter = new OAuth2AuthorizationRequestRedirectFilter(
-					OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder()),
-					authorizationRequestBaseUri);
+					this.getClientRegistrationRepository(), authorizationRequestBaseUri);
 		}
 		if (this.authorizationEndpointConfig.authorizationRequestRepository != null) {
 			authorizationRequestFilter
@@ -440,6 +444,16 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 		return new AntPathRequestMatcher(loginProcessingUrl);
 	}
 
+	private ClientRegistrationRepository getClientRegistrationRepository() {
+		return (this.clientRegistrationRepository != null) ? this.clientRegistrationRepository
+				: OAuth2ClientConfigurerUtils.getClientRegistrationRepository(this.getBuilder());
+	}
+
+	private OAuth2AuthorizedClientRepository getAuthorizedClientRepository() {
+		return (this.authorizedClientRepository != null) ? this.authorizedClientRepository
+				: OAuth2ClientConfigurerUtils.getAuthorizedClientRepository(this.getBuilder());
+	}
+
 	@SuppressWarnings("unchecked")
 	private JwtDecoderFactory<ClientRegistration> getJwtDecoderFactoryBean() {
 		ResolvableType type = ResolvableType.forClassWithGenerics(JwtDecoderFactory.class, ClientRegistration.class);
@@ -526,8 +540,7 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	@SuppressWarnings("unchecked")
 	private Map<String, String> getLoginLinks() {
 		Iterable<ClientRegistration> clientRegistrations = null;
-		ClientRegistrationRepository clientRegistrationRepository = OAuth2ClientConfigurerUtils
-			.getClientRegistrationRepository(this.getBuilder());
+		ClientRegistrationRepository clientRegistrationRepository = this.getClientRegistrationRepository();
 		ResolvableType type = ResolvableType.forInstance(clientRegistrationRepository).as(Iterable.class);
 		if (type != ResolvableType.NONE && ClientRegistration.class.isAssignableFrom(type.resolveGenerics()[0])) {
 			clientRegistrations = (Iterable<ClientRegistration>) clientRegistrationRepository;

+ 92 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2ClientConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -75,6 +75,7 @@ import static org.mockito.ArgumentMatchers.anyString;
 import static org.mockito.BDDMockito.given;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.springframework.security.config.Customizer.withDefaults;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user;
@@ -301,6 +302,49 @@ public class OAuth2ClientConfigurerTests {
 		verify(authorizationRequestResolver).resolve(any());
 	}
 
+	@Test
+	public void configureWhenOAuth2LoginBeansConfiguredThenNotShared() throws Exception {
+		this.spring.register(OAuth2ClientConfigWithOAuth2Login.class).autowire();
+		// Setup the Authorization Request in the session
+		Map<String, Object> attributes = new HashMap<>();
+		attributes.put(OAuth2ParameterNames.REGISTRATION_ID, this.registration1.getRegistrationId());
+		// @formatter:off
+		OAuth2AuthorizationRequest authorizationRequest = OAuth2AuthorizationRequest.authorizationCode()
+			.authorizationUri(this.registration1.getProviderDetails().getAuthorizationUri())
+			.clientId(this.registration1.getClientId())
+			.redirectUri("http://localhost/client-1")
+			.state("state")
+			.attributes(attributes)
+			.build();
+		// @formatter:on
+		AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository = new HttpSessionOAuth2AuthorizationRequestRepository();
+		MockHttpServletRequest request = new MockHttpServletRequest("GET", "");
+		MockHttpServletResponse response = new MockHttpServletResponse();
+		authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, request, response);
+		MockHttpSession session = (MockHttpSession) request.getSession();
+		String principalName = "user1";
+		TestingAuthenticationToken authentication = new TestingAuthenticationToken(principalName, "password");
+		// @formatter:off
+		MockHttpServletRequestBuilder clientRequest = get("/client-1")
+			.param(OAuth2ParameterNames.CODE, "code")
+			.param(OAuth2ParameterNames.STATE, "state")
+			.with(authentication(authentication))
+			.session(session);
+		this.mockMvc.perform(clientRequest)
+			.andExpect(status().is3xxRedirection())
+			.andExpect(redirectedUrl("http://localhost/client-1"));
+		// @formatter:on
+		OAuth2AuthorizedClient authorizedClient = authorizedClientRepository
+			.loadAuthorizedClient(this.registration1.getRegistrationId(), authentication, request);
+		assertThat(authorizedClient).isNotNull();
+		// Ensure shared objects set for OAuth2 Client are not used
+		ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
+			.getBean(ClientRegistrationRepository.class);
+		OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext()
+			.getBean(OAuth2AuthorizedClientRepository.class);
+		verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository);
+	}
+
 	@EnableWebSecurity
 	@Configuration
 	@EnableWebMvc
@@ -388,4 +432,51 @@ public class OAuth2ClientConfigurerTests {
 
 	}
 
+	@Configuration
+	@EnableWebSecurity
+	@EnableWebMvc
+	static class OAuth2ClientConfigWithOAuth2Login {
+
+		private final ClientRegistrationRepository clientRegistrationRepository = mock(
+				ClientRegistrationRepository.class);
+
+		private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock(
+				OAuth2AuthorizedClientRepository.class);
+
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.authorizeHttpRequests((authorize) -> authorize
+					.anyRequest().authenticated()
+				)
+				.oauth2Client((oauth2Client) -> oauth2Client
+					.clientRegistrationRepository(OAuth2ClientConfigurerTests.clientRegistrationRepository)
+					.authorizedClientService(OAuth2ClientConfigurerTests.authorizedClientService)
+					.authorizationCodeGrant((authorizationCode) -> authorizationCode
+						.authorizationRequestResolver(authorizationRequestResolver)
+						.authorizationRedirectStrategy(authorizationRedirectStrategy)
+						.accessTokenResponseClient(accessTokenResponseClient)
+					)
+				)
+				.oauth2Login((oauth2Login) -> oauth2Login
+					.clientRegistrationRepository(this.clientRegistrationRepository)
+					.authorizedClientRepository(this.authorizedClientRepository)
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		ClientRegistrationRepository clientRegistrationRepository() {
+			return this.clientRegistrationRepository;
+		}
+
+		@Bean
+		OAuth2AuthorizedClientRepository authorizedClientRepository() {
+			return this.authorizedClientRepository;
+		}
+
+	}
+
 }

+ 67 - 1
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OAuth2LoginConfigurerTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2024 the original author or authors.
+ * Copyright 2002-2025 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.
@@ -73,7 +73,9 @@ import org.springframework.security.oauth2.client.userinfo.OAuth2UserRequest;
 import org.springframework.security.oauth2.client.userinfo.OAuth2UserService;
 import org.springframework.security.oauth2.client.web.AuthorizationRequestRepository;
 import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizationRequestRepository;
+import org.springframework.security.oauth2.client.web.HttpSessionOAuth2AuthorizedClientRepository;
 import org.springframework.security.oauth2.client.web.OAuth2AuthorizationRequestResolver;
+import org.springframework.security.oauth2.client.web.OAuth2AuthorizedClientRepository;
 import org.springframework.security.oauth2.core.OAuth2AccessToken;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AccessTokenResponse;
 import org.springframework.security.oauth2.core.endpoint.OAuth2AuthorizationRequest;
@@ -115,6 +117,7 @@ import static org.mockito.BDDMockito.then;
 import static org.mockito.Mockito.atLeastOnce;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.verify;
+import static org.mockito.Mockito.verifyNoInteractions;
 import static org.springframework.security.config.annotation.SecurityContextChangedListenerArgumentMatchers.setAuthentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.authentication;
 import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
@@ -669,6 +672,30 @@ public class OAuth2LoginConfigurerTests {
 			.collect(Collectors.toList())).isEmpty();
 	}
 
+	@Test
+	public void oidcLoginWhenOAuth2ClientBeansConfiguredThenNotShared() throws Exception {
+		this.spring.register(OAuth2LoginConfigWithOAuth2Client.class, JwtDecoderFactoryConfig.class).autowire();
+		OAuth2AuthorizationRequest authorizationRequest = createOAuth2AuthorizationRequest("openid");
+		this.authorizationRequestRepository.saveAuthorizationRequest(authorizationRequest, this.request, this.response);
+		this.request.setParameter("code", "code123");
+		this.request.setParameter("state", authorizationRequest.getState());
+		this.springSecurityFilterChain.doFilter(this.request, this.response, this.filterChain);
+		Authentication authentication = this.securityContextRepository
+			.loadContext(new HttpRequestResponseHolder(this.request, this.response))
+			.getAuthentication();
+		assertThat(authentication.getAuthorities()).hasSize(1);
+		assertThat(authentication.getAuthorities()).first()
+			.isInstanceOf(OidcUserAuthority.class)
+			.hasToString("OIDC_USER");
+
+		// Ensure shared objects set for OAuth2 Client are not used
+		ClientRegistrationRepository clientRegistrationRepository = this.spring.getContext()
+			.getBean(ClientRegistrationRepository.class);
+		OAuth2AuthorizedClientRepository authorizedClientRepository = this.spring.getContext()
+			.getBean(OAuth2AuthorizedClientRepository.class);
+		verifyNoInteractions(clientRegistrationRepository, authorizedClientRepository);
+	}
+
 	private void loadConfig(Class<?>... configs) {
 		AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
 		applicationContext.register(configs);
@@ -1192,6 +1219,45 @@ public class OAuth2LoginConfigurerTests {
 
 	}
 
+	@Configuration
+	@EnableWebSecurity
+	static class OAuth2LoginConfigWithOAuth2Client extends CommonLambdaSecurityFilterChainConfig {
+
+		private final ClientRegistrationRepository clientRegistrationRepository = mock(
+				ClientRegistrationRepository.class);
+
+		private final OAuth2AuthorizedClientRepository authorizedClientRepository = mock(
+				OAuth2AuthorizedClientRepository.class);
+
+		@Bean
+		SecurityFilterChain securityFilterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.oauth2Login((oauth2Login) -> oauth2Login
+					.clientRegistrationRepository(
+						new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))
+					.authorizedClientRepository(new HttpSessionOAuth2AuthorizedClientRepository())
+				)
+				.oauth2Client((oauth2Client) -> oauth2Client
+					.clientRegistrationRepository(this.clientRegistrationRepository)
+					.authorizedClientRepository(this.authorizedClientRepository)
+				);
+			// @formatter:on
+			return super.configureFilterChain(http);
+		}
+
+		@Bean
+		ClientRegistrationRepository clientRegistrationRepository() {
+			return this.clientRegistrationRepository;
+		}
+
+		@Bean
+		OAuth2AuthorizedClientRepository authorizedClientRepository() {
+			return this.authorizedClientRepository;
+		}
+
+	}
+
 	private abstract static class CommonSecurityFilterChainConfig {
 
 		SecurityFilterChain configureFilterChain(HttpSecurity http) throws Exception {