浏览代码

Merge branch '6.2.x'

Josh Cummings 1 年之前
父节点
当前提交
34526c3e01

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

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -582,6 +582,10 @@ public final class OAuth2LoginConfigurer<B extends HttpSecurityBuilder<B>>
 	}
 
 	private void configureOidcSessionRegistry(B http) {
+		if (http.getConfigurer(OidcLogoutConfigurer.class) == null
+				&& http.getSharedObject(OidcSessionRegistry.class) == null) {
+			return;
+		}
 		OidcSessionRegistry sessionRegistry = OAuth2ClientConfigurerUtils.getOidcSessionRegistry(http);
 		SessionManagementConfigurer<B> sessionConfigurer = http.getConfigurer(SessionManagementConfigurer.class);
 		if (sessionConfigurer != null) {

+ 1 - 1
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurer.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.

+ 13 - 7
config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2023 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -4362,8 +4362,10 @@ public class ServerHttpSecurity {
 
 			ReactiveAuthenticationManager manager = getAuthenticationManager();
 			ReactiveOidcSessionRegistry sessionRegistry = getOidcSessionRegistry();
-			AuthenticationWebFilter authenticationFilter = new OidcSessionRegistryAuthenticationWebFilter(manager,
-					authorizedClientRepository, sessionRegistry);
+			AuthenticationWebFilter authenticationFilter = (sessionRegistry != null)
+					? new OidcSessionRegistryAuthenticationWebFilter(manager, authorizedClientRepository,
+							sessionRegistry)
+					: new OAuth2LoginAuthenticationWebFilter(manager, authorizedClientRepository);
 			authenticationFilter.setRequiresAuthenticationMatcher(getAuthenticationMatcher());
 			authenticationFilter
 				.setServerAuthenticationConverter(getAuthenticationConverter(clientRegistrationRepository));
@@ -4372,8 +4374,10 @@ public class ServerHttpSecurity {
 			authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
 
 			setDefaultEntryPoints(http);
-			http.addFilterAfter(new OidcSessionRegistryWebFilter(sessionRegistry),
-					SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);
+			if (sessionRegistry != null) {
+				http.addFilterAfter(new OidcSessionRegistryWebFilter(sessionRegistry),
+						SecurityWebFiltersOrder.HTTP_HEADERS_WRITER);
+			}
 			http.addFilterAt(oauthRedirectFilter, SecurityWebFiltersOrder.HTTP_BASIC);
 			http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.AUTHENTICATION);
 		}
@@ -4419,6 +4423,9 @@ public class ServerHttpSecurity {
 		}
 
 		private ReactiveOidcSessionRegistry getOidcSessionRegistry() {
+			if (ServerHttpSecurity.this.oidcLogout == null && this.oidcSessionRegistry == null) {
+				return null;
+			}
 			if (this.oidcSessionRegistry == null) {
 				this.oidcSessionRegistry = getBeanOrNull(ReactiveOidcSessionRegistry.class);
 			}
@@ -4656,8 +4663,7 @@ public class ServerHttpSecurity {
 
 		}
 
-		private static final class OidcSessionRegistryAuthenticationWebFilter
-				extends OAuth2LoginAuthenticationWebFilter {
+		static final class OidcSessionRegistryAuthenticationWebFilter extends OAuth2LoginAuthenticationWebFilter {
 
 			private final Log logger = LogFactory.getLog(getClass());
 

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

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -22,6 +22,7 @@ import java.util.Collections;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
+import java.util.stream.Collectors;
 
 import org.apache.http.HttpHeaders;
 import org.junit.jupiter.api.AfterEach;
@@ -36,6 +37,7 @@ import org.springframework.context.ApplicationListener;
 import org.springframework.context.ConfigurableApplicationContext;
 import org.springframework.context.annotation.Bean;
 import org.springframework.context.annotation.Configuration;
+import org.springframework.context.event.SmartApplicationListener;
 import org.springframework.http.HttpStatus;
 import org.springframework.http.MediaType;
 import org.springframework.mock.web.MockFilterChain;
@@ -48,6 +50,7 @@ import org.springframework.security.config.annotation.web.configuration.EnableWe
 import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
+import org.springframework.security.context.DelegatingApplicationListener;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.GrantedAuthority;
 import org.springframework.security.core.authority.AuthorityUtils;
@@ -55,9 +58,11 @@ import org.springframework.security.core.authority.SimpleGrantedAuthority;
 import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
 import org.springframework.security.core.context.SecurityContextChangedListener;
 import org.springframework.security.core.context.SecurityContextHolderStrategy;
+import org.springframework.security.core.session.SessionDestroyedEvent;
 import org.springframework.security.oauth2.client.authentication.OAuth2AuthenticationToken;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
+import org.springframework.security.oauth2.client.oidc.session.OidcSessionRegistry;
 import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
 import org.springframework.security.oauth2.client.oidc.web.logout.OidcClientInitiatedLogoutSuccessHandler;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
@@ -95,7 +100,9 @@ import org.springframework.security.web.authentication.HttpStatusEntryPoint;
 import org.springframework.security.web.context.HttpRequestResponseHolder;
 import org.springframework.security.web.context.HttpSessionSecurityContextRepository;
 import org.springframework.security.web.context.SecurityContextRepository;
+import org.springframework.security.web.session.HttpSessionDestroyedEvent;
 import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
+import org.springframework.test.util.ReflectionTestUtils;
 import org.springframework.test.web.servlet.MockMvc;
 import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;
 
@@ -150,10 +157,10 @@ public class OAuth2LoginConfigurerTests {
 	@Autowired
 	private FilterChainProxy springSecurityFilterChain;
 
-	@Autowired
+	@Autowired(required = false)
 	private AuthorizationRequestRepository<OAuth2AuthorizationRequest> authorizationRequestRepository;
 
-	@Autowired
+	@Autowired(required = false)
 	SecurityContextRepository securityContextRepository;
 
 	public final SpringTestContext spring = new SpringTestContext(this);
@@ -642,6 +649,26 @@ public class OAuth2LoginConfigurerTests {
 			.andExpect(redirectedUrl("https://logout?id_token_hint=id-token"));
 	}
 
+	@Test
+	public void configureWhenOidcSessionStrategyThenUses() {
+		this.spring.register(OAuth2LoginWithOidcSessionRegistry.class).autowire();
+		OidcSessionRegistry registry = this.spring.getContext().getBean(OidcSessionRegistry.class);
+		this.spring.getContext().publishEvent(new HttpSessionDestroyedEvent(this.request.getSession()));
+		verify(registry).removeSessionInformation(this.request.getSession().getId());
+	}
+
+	// gh-14558
+	@Test
+	public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() {
+		this.spring.register(OAuth2LoginConfig.class).autowire();
+		DelegatingApplicationListener listener = this.spring.getContext().getBean(DelegatingApplicationListener.class);
+		List<SmartApplicationListener> listeners = (List<SmartApplicationListener>) ReflectionTestUtils
+			.getField(listener, "listeners");
+		assertThat(listeners.stream()
+			.filter((l) -> l.supportsEventType(SessionDestroyedEvent.class))
+			.collect(Collectors.toList())).isEmpty();
+	}
+
 	private void loadConfig(Class<?>... configs) {
 		AnnotationConfigWebApplicationContext applicationContext = new AnnotationConfigWebApplicationContext();
 		applicationContext.register(configs);
@@ -1117,6 +1144,32 @@ public class OAuth2LoginConfigurerTests {
 
 	}
 
+	@Configuration
+	@EnableWebSecurity
+	static class OAuth2LoginWithOidcSessionRegistry {
+
+		private final OidcSessionRegistry registry = mock(OidcSessionRegistry.class);
+
+		@Bean
+		SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.oauth2Login((oauth2) -> oauth2
+					.clientRegistrationRepository(
+							new InMemoryClientRegistrationRepository(GOOGLE_CLIENT_REGISTRATION))
+					.oidcSessionRegistry(this.registry)
+				);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		OidcSessionRegistry oidcSessionRegistry() {
+			return this.registry;
+		}
+
+	}
+
 	@Configuration
 	@EnableWebSecurity
 	static class OAuth2LoginWithXHREntryPointConfig extends CommonSecurityFilterChainConfig {

+ 70 - 1
config/src/test/java/org/springframework/security/config/web/server/OAuth2LoginTests.java

@@ -1,5 +1,5 @@
 /*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -34,11 +34,13 @@ import org.springframework.http.HttpHeaders;
 import org.springframework.security.authentication.ReactiveAuthenticationManager;
 import org.springframework.security.authentication.TestingAuthenticationToken;
 import org.springframework.security.authentication.UserDetailsRepositoryReactiveAuthenticationManager;
+import org.springframework.security.config.Customizer;
 import org.springframework.security.config.annotation.web.reactive.EnableWebFluxSecurity;
 import org.springframework.security.config.oauth2.client.CommonOAuth2Provider;
 import org.springframework.security.config.test.SpringTestContext;
 import org.springframework.security.config.test.SpringTestContextExtension;
 import org.springframework.security.config.users.ReactiveAuthenticationTestConfiguration;
+import org.springframework.security.config.web.server.ServerHttpSecurity.OAuth2LoginSpec.OidcSessionRegistryAuthenticationWebFilter;
 import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.authority.AuthorityUtils;
@@ -54,10 +56,12 @@ import org.springframework.security.oauth2.client.authentication.OAuth2LoginAuth
 import org.springframework.security.oauth2.client.endpoint.OAuth2AuthorizationCodeGrantRequest;
 import org.springframework.security.oauth2.client.endpoint.ReactiveOAuth2AccessTokenResponseClient;
 import org.springframework.security.oauth2.client.oidc.authentication.OidcAuthorizationCodeReactiveAuthenticationManager;
+import org.springframework.security.oauth2.client.oidc.server.session.ReactiveOidcSessionRegistry;
 import org.springframework.security.oauth2.client.oidc.userinfo.OidcUserRequest;
 import org.springframework.security.oauth2.client.oidc.web.server.logout.OidcClientInitiatedServerLogoutSuccessHandler;
 import org.springframework.security.oauth2.client.registration.ClientRegistration;
 import org.springframework.security.oauth2.client.registration.InMemoryReactiveClientRegistrationRepository;
+import org.springframework.security.oauth2.client.registration.ReactiveClientRegistrationRepository;
 import org.springframework.security.oauth2.client.registration.TestClientRegistrations;
 import org.springframework.security.oauth2.client.userinfo.ReactiveOAuth2UserService;
 import org.springframework.security.oauth2.client.web.server.ServerAuthorizationRequestRepository;
@@ -576,6 +580,27 @@ public class OAuth2LoginTests {
 		// @formatter:on
 	}
 
+	@Test
+	public void oauth2LoginWhenOidcSessionRegistryThenUses() {
+		this.spring.register(OAuth2LoginWithOidcSessionRegistry.class).autowire();
+		SecurityWebFilterChain chain = this.spring.getContext().getBean(SecurityWebFilterChain.class);
+		assertThat(chain.getWebFilters()
+			.filter((filter) -> filter instanceof OidcSessionRegistryAuthenticationWebFilter)
+			.collectList()
+			.block()).isNotEmpty();
+	}
+
+	// gh-14558
+	@Test
+	public void oauth2LoginWhenDefaultsThenNoOidcSessionRegistry() {
+		this.spring.register(OAuth2LoginWithSingleClientRegistrations.class, OAuth2LoginConfig.class).autowire();
+		SecurityWebFilterChain chain = this.spring.getContext().getBean(SecurityWebFilterChain.class);
+		assertThat(chain.getWebFilters()
+			.filter((filter) -> filter instanceof OidcSessionRegistryAuthenticationWebFilter)
+			.collectList()
+			.block()).isEmpty();
+	}
+
 	Mono<SecurityContext> authentication(Authentication authentication) {
 		SecurityContext context = new SecurityContextImpl();
 		context.setAuthentication(authentication);
@@ -624,6 +649,21 @@ public class OAuth2LoginTests {
 
 	}
 
+	@EnableWebFlux
+	static class OAuth2LoginConfig {
+
+		@Bean
+		SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
+			// @formatter:off
+			http
+				.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
+				.oauth2Login(Customizer.withDefaults());
+			// @formatter:on
+			return http.build();
+		}
+
+	}
+
 	@EnableWebFlux
 	static class OAuth2AuthorizeWithMockObjectsConfig {
 
@@ -892,6 +932,35 @@ public class OAuth2LoginTests {
 
 	}
 
+	@Configuration
+	@EnableWebFluxSecurity
+	static class OAuth2LoginWithOidcSessionRegistry {
+
+		private final ReactiveOidcSessionRegistry registry = mock(ReactiveOidcSessionRegistry.class);
+
+		private final ReactiveClientRegistrationRepository clients = new InMemoryReactiveClientRegistrationRepository(
+				TestClientRegistrations.clientRegistration().build());
+
+		@Bean
+		SecurityWebFilterChain filterChain(ServerHttpSecurity http) {
+			// @formatter:off
+			http
+					.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
+					.oauth2Login((oauth2) -> oauth2
+							.clientRegistrationRepository(this.clients)
+							.oidcSessionRegistry(this.registry)
+					);
+			// @formatter:on
+			return http.build();
+		}
+
+		@Bean
+		ReactiveOidcSessionRegistry oidcSessionRegistry() {
+			return this.registry;
+		}
+
+	}
+
 	static class GitHubWebFilter implements WebFilter {
 
 		@Override

+ 27 - 0
docs/modules/ROOT/pages/servlet/oauth2/login/logout.adoc

@@ -170,6 +170,33 @@ open fun filterChain(http: HttpSecurity): SecurityFilterChain {
 ----
 ======
 
+Then, you need a way listen to events published by Spring Security to remove old `OidcSessionInformation` entries, like so:
+
+[tabs]
+======
+Java::
++
+[source=java,role="primary"]
+----
+@Bean
+public HttpSessionEventListener sessionEventListener() {
+    return new HttpSessionEventListener();
+}
+----
+
+Kotlin::
++
+[source=kotlin,role="secondary"]
+----
+@Bean
+open fun sessionEventListener(): HttpSessionEventListener {
+    return HttpSessionEventListener()
+}
+----
+======
+
+This will make so that if `HttpSession#invalidate` is called, then the session is also removed from memory.
+
 And that's it!
 
 This will stand up the endpoint `+/logout/connect/back-channel/{registrationId}+` which the OIDC Provider can request to invalidate a given session of an end user in your application.