Skip to content

Commit 1a3c64e

Browse files
committed
feat: Add oidcBackChannelLogout DSL and deprecate backChannel() method
- Introduced a new HttpSecurity method: oidcBackChannelLogout(Customizer.withDefaults()) to simplify OIDC Back-Channel Logout configuration. - Modified OidcLogoutConfigurer: marked backChannel(Customizer<...>) as deprecated (since 6.2, forRemoval = true) and updated its JavaDoc to recommend using the new DSL method. - Added tests (oidcBackChannelLogoutWhenDefaultsThenRemotelyInvalidatesSessions) to verify that the new DSL correctly registers OidcBackChannelLogoutFilter and invalidates sessions. Closes spring-projectsgh-15817 Signed-off-by: Minje Kim <[email protected]>
1 parent 401e237 commit 1a3c64e

File tree

3 files changed

+68
-85
lines changed

3 files changed

+68
-85
lines changed

Diff for: config/src/main/java/org/springframework/security/config/annotation/web/builders/HttpSecurity.java

+15-33
Original file line numberDiff line numberDiff line change
@@ -16,18 +16,8 @@
1616

1717
package org.springframework.security.config.annotation.web.builders;
1818

19-
import java.io.IOException;
20-
import java.util.ArrayList;
21-
import java.util.List;
22-
import java.util.Map;
23-
24-
import jakarta.servlet.Filter;
25-
import jakarta.servlet.FilterChain;
26-
import jakarta.servlet.ServletException;
27-
import jakarta.servlet.ServletRequest;
28-
import jakarta.servlet.ServletResponse;
19+
import jakarta.servlet.*;
2920
import jakarta.servlet.http.HttpServletRequest;
30-
3121
import org.springframework.beans.factory.NoSuchBeanDefinitionException;
3222
import org.springframework.beans.factory.ObjectProvider;
3323
import org.springframework.context.ApplicationContext;
@@ -48,29 +38,8 @@
4838
import org.springframework.security.config.annotation.web.RequestMatcherFactory;
4939
import org.springframework.security.config.annotation.web.configuration.EnableWebSecurity;
5040
import org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration;
51-
import org.springframework.security.config.annotation.web.configurers.AnonymousConfigurer;
52-
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer;
41+
import org.springframework.security.config.annotation.web.configurers.*;
5342
import org.springframework.security.config.annotation.web.configurers.AuthorizeHttpRequestsConfigurer.AuthorizationManagerRequestMatcherRegistry;
54-
import org.springframework.security.config.annotation.web.configurers.ChannelSecurityConfigurer;
55-
import org.springframework.security.config.annotation.web.configurers.CorsConfigurer;
56-
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
57-
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
58-
import org.springframework.security.config.annotation.web.configurers.ExpressionUrlAuthorizationConfigurer;
59-
import org.springframework.security.config.annotation.web.configurers.FormLoginConfigurer;
60-
import org.springframework.security.config.annotation.web.configurers.HeadersConfigurer;
61-
import org.springframework.security.config.annotation.web.configurers.HttpBasicConfigurer;
62-
import org.springframework.security.config.annotation.web.configurers.HttpsRedirectConfigurer;
63-
import org.springframework.security.config.annotation.web.configurers.JeeConfigurer;
64-
import org.springframework.security.config.annotation.web.configurers.LogoutConfigurer;
65-
import org.springframework.security.config.annotation.web.configurers.PasswordManagementConfigurer;
66-
import org.springframework.security.config.annotation.web.configurers.PortMapperConfigurer;
67-
import org.springframework.security.config.annotation.web.configurers.RememberMeConfigurer;
68-
import org.springframework.security.config.annotation.web.configurers.RequestCacheConfigurer;
69-
import org.springframework.security.config.annotation.web.configurers.SecurityContextConfigurer;
70-
import org.springframework.security.config.annotation.web.configurers.ServletApiConfigurer;
71-
import org.springframework.security.config.annotation.web.configurers.SessionManagementConfigurer;
72-
import org.springframework.security.config.annotation.web.configurers.WebAuthnConfigurer;
73-
import org.springframework.security.config.annotation.web.configurers.X509Configurer;
7443
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2ClientConfigurer;
7544
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OAuth2LoginConfigurer;
7645
import org.springframework.security.config.annotation.web.configurers.oauth2.client.OidcLogoutConfigurer;
@@ -103,6 +72,11 @@
10372
import org.springframework.web.filter.CorsFilter;
10473
import org.springframework.web.servlet.handler.HandlerMappingIntrospector;
10574

75+
import java.io.IOException;
76+
import java.util.ArrayList;
77+
import java.util.List;
78+
import java.util.Map;
79+
10680
/**
10781
* A {@link HttpSecurity} is similar to Spring Security's XML &lt;http&gt; element in the
10882
* namespace configuration. It allows configuring web based security for specific http
@@ -2870,6 +2844,14 @@ public HttpSecurity oidcLogout(Customizer<OidcLogoutConfigurer<HttpSecurity>> oi
28702844
return HttpSecurity.this;
28712845
}
28722846

2847+
public HttpSecurity oidcBackChannelLogout(Customizer<OidcLogoutConfigurer<HttpSecurity>> oidcBackChannelLogoutCustomizer)
2848+
throws Exception {
2849+
oidcBackChannelLogoutCustomizer.customize(
2850+
getOrApply(new OidcLogoutConfigurer<>()).backChannel(Customizer.withDefaults())
2851+
);
2852+
return this;
2853+
}
2854+
28732855
/**
28742856
* Configures OAuth 2.0 Client support.
28752857
* @return the {@link OAuth2ClientConfigurer} for further customizations

Diff for: config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurer.java

+6-33
Original file line numberDiff line numberDiff line change
@@ -16,12 +16,8 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
1818

19-
import java.util.function.Consumer;
20-
import java.util.function.Function;
21-
2219
import jakarta.servlet.http.HttpServletRequest;
2320
import jakarta.servlet.http.HttpServletResponse;
24-
2521
import org.springframework.context.ApplicationContext;
2622
import org.springframework.security.authentication.AuthenticationManager;
2723
import org.springframework.security.authentication.ProviderManager;
@@ -40,6 +36,9 @@
4036
import org.springframework.security.web.csrf.CsrfFilter;
4137
import org.springframework.util.Assert;
4238

39+
import java.util.function.Consumer;
40+
import java.util.function.Function;
41+
4342
/**
4443
* An {@link AbstractHttpConfigurer} for OIDC Logout flows
4544
*
@@ -102,7 +101,10 @@ public OidcLogoutConfigurer<B> oidcSessionRegistry(OidcSessionRegistry oidcSessi
102101
/**
103102
* Configure OIDC Back-Channel Logout using the provided {@link Consumer}
104103
* @return the {@link OidcLogoutConfigurer} for further configuration
104+
* @deprecated For removal in a future release. Use
105+
* {@link HttpSecurity#oidcBackChannelLogout(Customizer)} instead.
105106
*/
107+
@Deprecated(since = "6.2", forRemoval = true)
106108
public OidcLogoutConfigurer<B> backChannel(Customizer<BackChannelLogoutConfigurer> backChannelLogoutConfigurer) {
107109
if (this.backChannel == null) {
108110
this.backChannel = new BackChannelLogoutConfigurer();
@@ -157,35 +159,6 @@ private LogoutHandler logoutHandler(B http) {
157159
return logoutHandler;
158160
}
159161

160-
/**
161-
* Use this endpoint when invoking a back-channel logout.
162-
*
163-
* <p>
164-
* The resulting {@link LogoutHandler} will {@code POST} the session cookie and
165-
* CSRF token to this endpoint to invalidate the corresponding end-user session.
166-
*
167-
* <p>
168-
* Supports URI templates like {@code {baseUrl}}, {@code {baseScheme}}, and
169-
* {@code {basePort}}.
170-
*
171-
* <p>
172-
* By default, the URI is set to
173-
* {@code {baseScheme}://localhost{basePort}/logout}, meaning that the scheme and
174-
* port of the original back-channel request is preserved, while the host and
175-
* endpoint are changed.
176-
*
177-
* <p>
178-
* If you are using Spring Security for the logout endpoint, the path part of this
179-
* URI should match the value configured there.
180-
*
181-
* <p>
182-
* Otherwise, this is handy in the event that your server configuration means that
183-
* the scheme, server name, or port in the {@code Host} header are different from
184-
* how you would address the same server internally.
185-
* @param logoutUri the URI to request logout on the back-channel
186-
* @return the {@link BackChannelLogoutConfigurer} for further customizations
187-
* @since 6.2.4
188-
*/
189162
public BackChannelLogoutConfigurer logoutUri(String logoutUri) {
190163
this.logoutHandler = (http) -> {
191164
OidcBackChannelLogoutHandler logoutHandler = new OidcBackChannelLogoutHandler(

Diff for: config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/client/OidcLogoutConfigurerTests.java

+47-19
Original file line numberDiff line numberDiff line change
@@ -16,16 +16,6 @@
1616

1717
package org.springframework.security.config.annotation.web.configurers.oauth2.client;
1818

19-
import java.io.IOException;
20-
import java.security.KeyPair;
21-
import java.security.KeyPairGenerator;
22-
import java.security.interfaces.RSAPublicKey;
23-
import java.time.Instant;
24-
import java.util.List;
25-
import java.util.Map;
26-
import java.util.concurrent.ConcurrentHashMap;
27-
import java.util.function.Consumer;
28-
2919
import com.nimbusds.jose.jwk.JWKSet;
3020
import com.nimbusds.jose.jwk.RSAKey;
3121
import com.nimbusds.jose.jwk.source.ImmutableJWKSet;
@@ -44,7 +34,6 @@
4434
import org.htmlunit.util.UrlUtils;
4535
import org.junit.jupiter.api.Test;
4636
import org.junit.jupiter.api.extension.ExtendWith;
47-
4837
import org.springframework.beans.factory.ObjectProvider;
4938
import org.springframework.beans.factory.annotation.Autowired;
5039
import org.springframework.context.annotation.Bean;
@@ -75,11 +64,7 @@
7564
import org.springframework.security.oauth2.core.oidc.TestOidcIdTokens;
7665
import org.springframework.security.oauth2.core.oidc.user.OidcUser;
7766
import org.springframework.security.oauth2.jose.jws.SignatureAlgorithm;
78-
import org.springframework.security.oauth2.jwt.JwsHeader;
79-
import org.springframework.security.oauth2.jwt.JwtClaimsSet;
80-
import org.springframework.security.oauth2.jwt.JwtEncoder;
81-
import org.springframework.security.oauth2.jwt.JwtEncoderParameters;
82-
import org.springframework.security.oauth2.jwt.NimbusJwtEncoder;
67+
import org.springframework.security.oauth2.jwt.*;
8368
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
8469
import org.springframework.security.web.SecurityFilterChain;
8570
import org.springframework.security.web.authentication.logout.LogoutHandler;
@@ -92,14 +77,22 @@
9277
import org.springframework.web.bind.annotation.RestController;
9378
import org.springframework.web.servlet.config.annotation.EnableWebMvc;
9479

80+
import java.io.IOException;
81+
import java.security.KeyPair;
82+
import java.security.KeyPairGenerator;
83+
import java.security.interfaces.RSAPublicKey;
84+
import java.time.Instant;
85+
import java.util.List;
86+
import java.util.Map;
87+
import java.util.concurrent.ConcurrentHashMap;
88+
import java.util.function.Consumer;
89+
9590
import static org.assertj.core.api.Assertions.assertThat;
9691
import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
9792
import static org.hamcrest.Matchers.containsString;
9893
import static org.mockito.ArgumentMatchers.any;
9994
import static org.mockito.BDDMockito.willThrow;
100-
import static org.mockito.Mockito.mock;
101-
import static org.mockito.Mockito.spy;
102-
import static org.mockito.Mockito.verify;
95+
import static org.mockito.Mockito.*;
10396
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.csrf;
10497
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.httpBasic;
10598
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
@@ -312,6 +305,24 @@ void logoutWhenProviderIssuerMissingThenThrowIllegalArgumentException() throws E
312305
.param("logout_token", logoutToken)));
313306
}
314307

308+
@Test
309+
void oidcBackChannelLogoutWhenDefaultsThenRemotelyInvalidatesSessions() throws Exception {
310+
this.spring.register(WebServerConfig.class, OidcProviderConfig.class, WithOidcBackChannelDslConfig.class)
311+
.autowire();
312+
String registrationId = this.clientRegistration.getRegistrationId();
313+
MockHttpSession session = login();
314+
String logoutToken = this.mvc.perform(get("/token/logout").session(session))
315+
.andExpect(status().isOk())
316+
.andReturn()
317+
.getResponse()
318+
.getContentAsString();
319+
this.mvc.perform(post(this.web.url("/logout/connect/back-channel/" + registrationId).toString())
320+
.param("logout_token", logoutToken))
321+
.andExpect(status().isOk());
322+
this.mvc.perform(get("/token/logout").session(session))
323+
.andExpect(status().isUnauthorized());
324+
}
325+
315326
private MockHttpSession login() throws Exception {
316327
MockMvcDispatcher dispatcher = (MockMvcDispatcher) this.web.getDispatcher();
317328
this.mvc.perform(get("/token/logout")).andExpect(status().isUnauthorized());
@@ -739,6 +750,23 @@ void shutdown() throws IOException {
739750

740751
}
741752

753+
@Configuration
754+
@EnableWebSecurity
755+
@Import(RegistrationConfig.class)
756+
static class WithOidcBackChannelDslConfig {
757+
758+
@Bean
759+
@Order(1)
760+
SecurityFilterChain filters(HttpSecurity http) throws Exception {
761+
http
762+
.authorizeHttpRequests((authorize) -> authorize.anyRequest().authenticated())
763+
.oauth2Login(Customizer.withDefaults())
764+
.oidcBackChannelLogout(Customizer.withDefaults());
765+
return http.build();
766+
}
767+
768+
}
769+
742770
private static class MockMvcDispatcher extends Dispatcher {
743771

744772
private final Map<String, MockHttpSession> session = new ConcurrentHashMap<>();

0 commit comments

Comments
 (0)