1
0
mirror of synced 2026-09-02 23:39:56 +00:00

Compare commits

...

10 Commits

Author SHA1 Message Date
Josh Cummings 3070c96594 Add authenticationSuccessHandler to Reactive Resource Server Kotlin DSL
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-09-01 18:14:07 -07:00
Josh Cummings 6eb7ef6e6d Update What's New
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-09-01 18:14:07 -07:00
Josh Cummings 988ba6b33c Align Formatting
Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-09-01 18:14:07 -07:00
Iain Henderson d1fb6141c1 Add authenticationSuccessHandler to Reactive Resource Server DSL
Signed-off-by: Iain Henderson <Iain.henderson@mac.com>
2026-09-01 18:14:07 -07:00
Josh Cummings 43f5ae5b21 Remove Auto-merge for .x Branches
Closes gh-19632

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-31 10:43:16 -06:00
Josh Cummings b2a6bbf314 Merge branch '7.1.x' 2026-08-31 10:42:04 -06:00
Josh Cummings a161421409 Turn Off Auto-merge for Maintenance Branch
This commit removes the auto-merge workflow on 7.1.x since it
is a maintenance branch.

Closes gh-19631

Signed-off-by: Josh Cummings <3627351+jzheaux@users.noreply.github.com>
2026-08-31 10:41:54 -06:00
Joe Grandja 36aa2ad744 Merge branch '7.1.x' 2026-08-31 12:02:21 -04:00
Joe Grandja 978eb4396a Polish gh-19585 2026-08-31 11:09:18 -04:00
Evgeniy Cheban 770c860d9d Ensure WebSession ID is not changed after token refresh (Reactive)
Closes gh-19424

Signed-off-by: Evgeniy Cheban <mister.cheban@gmail.com>
2026-08-31 10:11:13 -04:00
9 changed files with 344 additions and 5 deletions
+1 -2
View File
@@ -4,7 +4,6 @@ on:
pull_request:
branches:
- main
- '*.x'
- 'docs-build'
run-name: Merge Dependabot PR ${{ github.ref_name }}
@@ -14,4 +13,4 @@ jobs:
permissions: write-all
uses: spring-io/spring-github-workflows/.github/workflows/spring-merge-dependabot-pr.yml@0d3f15bb384839966a1ff5c4383731a2b747f24b # v7
with:
mergeArguments: --auto --rebase
mergeArguments: --auto --rebase
@@ -297,6 +297,7 @@ import org.springframework.web.util.pattern.PathPatternParser;
* @author Ankur Pathak
* @author Alexey Nesterov
* @author Yanming Zhou
* @author Iain Henderson
* @since 5.0
*/
public class ServerHttpSecurity {
@@ -4138,6 +4139,8 @@ public class ServerHttpSecurity {
private ServerAuthenticationFailureHandler authenticationFailureHandler;
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler();
private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter();
@@ -4186,6 +4189,20 @@ public class ServerHttpSecurity {
return this;
}
/**
* Configures the {@link ServerAuthenticationSuccessHandler} to use. The default
* is {@link WebFilterChainServerAuthenticationSuccessHandler}
* @param authenticationSuccessHandler the
* {@link ServerAuthenticationSuccessHandler} to use
* @return the {@link OAuth2ClientSpec} to customize
* @since 7.2
*/
public OAuth2ResourceServerSpec authenticationSuccessHandler(
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
this.authenticationSuccessHandler = authenticationSuccessHandler;
return this;
}
/**
* Configures the {@link ServerAuthenticationConverter} to use for requests
* authenticating with
@@ -4254,6 +4271,7 @@ public class ServerHttpSecurity {
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(this.authenticationManagerResolver);
oauth2.setServerAuthenticationConverter(this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
else if (this.jwt != null) {
@@ -4313,6 +4331,13 @@ public class ServerHttpSecurity {
return new ServerAuthenticationEntryPointFailureHandler(this.entryPoint);
}
private ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
if (this.authenticationSuccessHandler != null) {
return this.authenticationSuccessHandler;
}
return new WebFilterChainServerAuthenticationSuccessHandler();
}
/**
* Configures JWT Resource Server Support
*/
@@ -4387,6 +4412,7 @@ public class ServerHttpSecurity {
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
@@ -4519,6 +4545,7 @@ public class ServerHttpSecurity {
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
}
@@ -20,6 +20,7 @@ import org.springframework.security.authentication.ReactiveAuthenticationManager
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
import org.springframework.web.server.ServerWebExchange
@@ -35,6 +36,8 @@ import org.springframework.web.server.ServerWebExchange
* @property bearerTokenConverter the [ServerAuthenticationConverter] to use for requests authenticating with
* Bearer Tokens.
* @property authenticationManagerResolver the [ReactiveAuthenticationManagerResolver] to use.
* @property authenticationSuccessHandler the [ServerAuthenticationSuccessHandler] to use after
* authentication success.
*/
@ServerSecurityMarker
class ServerOAuth2ResourceServerDsl {
@@ -43,6 +46,7 @@ class ServerOAuth2ResourceServerDsl {
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
var bearerTokenConverter: ServerAuthenticationConverter? = null
var authenticationManagerResolver: ReactiveAuthenticationManagerResolver<ServerWebExchange>? = null
var authenticationSuccessHandler: ServerAuthenticationSuccessHandler? = null
private var jwt: ((ServerHttpSecurity.OAuth2ResourceServerSpec.JwtSpec) -> Unit)? = null
private var opaqueToken: ((ServerHttpSecurity.OAuth2ResourceServerSpec.OpaqueTokenSpec) -> Unit)? = null
@@ -115,6 +119,7 @@ class ServerOAuth2ResourceServerDsl {
authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
bearerTokenConverter?.also { oauth2ResourceServer.bearerTokenConverter(bearerTokenConverter) }
authenticationManagerResolver?.also { oauth2ResourceServer.authenticationManagerResolver(authenticationManagerResolver!!) }
authenticationSuccessHandler?.also { oauth2ResourceServer.authenticationSuccessHandler(authenticationSuccessHandler) }
jwt?.also { oauth2ResourceServer.jwt(jwt) }
opaqueToken?.also { oauth2ResourceServer.opaqueToken(opaqueToken) }
}
@@ -73,9 +73,11 @@ import org.springframework.security.oauth2.server.resource.authentication.Reacti
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
import org.springframework.security.web.server.SecurityWebFilterChain;
import org.springframework.security.web.server.WebFilterExchange;
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
import org.springframework.test.web.reactive.server.WebTestClient;
import org.springframework.web.bind.annotation.GetMapping;
@@ -371,6 +373,79 @@ public class OAuth2ResourceServerSpecTests {
verify(handler).onAuthenticationFailure(any(), any());
}
@Test
public void getWhenUsingCustomAuthenticationSuccessHandlerThenUsesIsAccordingly() {
this.spring.register(CustomAuthenticationSuccessHandlerAuthenticationManagerResolverConfig.class).autowire();
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
.getBean(ServerAuthenticationSuccessHandler.class);
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
.getBean(ReactiveAuthenticationManager.class);
given(authenticationManager.authenticate(any()))
.willAnswer((input) -> Mono.just(input.getArgument(0, Authentication.class)));
given(handler.onAuthenticationSuccess(any(), any())).willAnswer((input) -> {
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
});
// @formatter:off
this.client.get()
.headers((headers) -> headers.setBearerAuth(this.messageReadToken))
.exchange()
.expectStatus().isUnauthorized();
// @formatter:on
verify(handler).onAuthenticationSuccess(any(), any());
}
@Test
public void getWhenUsingCustomAuthenticationSuccessHandlerWithJwtThenUsesIsAccordingly() {
this.spring.register(CustomAuthenticationSuccessHandlerJwtConfig.class).autowire();
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
.getBean(ServerAuthenticationSuccessHandler.class);
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
.getBean(ReactiveAuthenticationManager.class);
given(authenticationManager.authenticate(any()))
.willAnswer((input) -> Mono.just(input.getArgument(0, Authentication.class)));
given(handler.onAuthenticationSuccess(any(), any())).willAnswer((input) -> {
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
});
// @formatter:off
this.client.get()
.headers((headers) -> headers.setBearerAuth(this.messageReadToken))
.exchange()
.expectStatus().isUnauthorized();
// @formatter:on
verify(handler).onAuthenticationSuccess(any(), any());
}
@Test
public void getWhenUsingCustomAuthenticationSuccessHandlerWIthOpaqueTokenThenUsesIsAccordingly() {
this.spring.register(CustomAuthenticationSuccessHandlerOpaqueTokenConfig.class, RootController.class)
.autowire();
this.spring.getContext()
.getBean(MockWebServer.class)
.setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active));
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
.getBean(ServerAuthenticationSuccessHandler.class);
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
.getBean(ReactiveAuthenticationManager.class);
given(authenticationManager.authenticate(any()))
.willAnswer((input) -> Mono.just(input.getArgument(0, Authentication.class)));
given(handler.onAuthenticationSuccess(any(), any())).willAnswer((input) -> {
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
});
// @formatter:off
this.client.get()
.headers((headers) -> headers
.setBearerAuth(this.messageReadToken)
)
.exchange()
.expectStatus().isOk();
// @formatter:on
verify(handler).onAuthenticationSuccess(any(), any());
}
@Test
public void postWhenSignedThenReturnsOk() {
this.spring.register(PublicKeyConfig.class, RootController.class).autowire();
@@ -950,6 +1025,111 @@ public class OAuth2ResourceServerSpecTests {
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomAuthenticationSuccessHandlerAuthenticationManagerResolverConfig {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2
.authenticationSuccessHandler(authenticationSuccessHandler())
.authenticationManagerResolver((exchange) -> Mono.just(authenticationManager()))
);
// @formatter:on
return http.build();
}
@Bean
ReactiveAuthenticationManager authenticationManager() {
return mock(ReactiveAuthenticationManager.class);
}
@Bean
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
return mock(ServerAuthenticationSuccessHandler.class);
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomAuthenticationSuccessHandlerJwtConfig {
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2
.authenticationSuccessHandler(authenticationSuccessHandler())
.jwt((jwt) -> jwt.authenticationManager(authenticationManager()))
);
// @formatter:on
return http.build();
}
@Bean
ReactiveAuthenticationManager authenticationManager() {
return mock(ReactiveAuthenticationManager.class);
}
@Bean
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
return mock(ServerAuthenticationSuccessHandler.class);
}
}
@Configuration
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomAuthenticationSuccessHandlerOpaqueTokenConfig {
private MockWebServer mockWebServer = new MockWebServer();
@Bean
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
String introspectionUri = mockWebServer().url("/introspect").toString();
// @formatter:off
http
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
.oauth2ResourceServer((oauth2) -> oauth2
.authenticationSuccessHandler(authenticationSuccessHandler())
.opaqueToken((opaqueToken) -> opaqueToken
.introspectionUri(introspectionUri)
.introspectionClientCredentials("client", "secret"))
);
// @formatter:on
return http.build();
}
@Bean
ReactiveAuthenticationManager authenticationManager() {
return mock(ReactiveAuthenticationManager.class);
}
@Bean
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
return mock(ServerAuthenticationSuccessHandler.class);
}
@Bean
MockWebServer mockWebServer() {
return this.mockWebServer;
}
@PreDestroy
void shutdown() throws IOException {
this.mockWebServer.shutdown();
}
}
@EnableWebFlux
@EnableWebFluxSecurity
static class CustomBearerTokenServerAuthenticationConverter {
@@ -17,6 +17,7 @@
package org.springframework.security.config.web.server
import io.mockk.every
import io.mockk.mockk
import io.mockk.mockkObject
import io.mockk.verify
import org.junit.jupiter.api.Test
@@ -37,6 +38,7 @@ import org.springframework.security.web.server.SecurityWebFilterChain
import org.springframework.security.web.server.WebFilterExchange
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler
import org.springframework.test.web.reactive.server.WebTestClient
import org.springframework.web.reactive.config.EnableWebFlux
@@ -183,6 +185,46 @@ class ServerOAuth2ResourceServerDslTests {
}
@Test
fun `request when custom authentication success handler then success handler used`() {
this.spring.register(AuthenticationSuccessHandlerConfig::class.java).autowire()
every {
AuthenticationSuccessHandlerConfig.SUCCESS_HANDLER.onAuthenticationSuccess(any(), any())
} returns Mono.empty()
this.client.get()
.uri("/")
.headers { it.setBearerAuth(validJwt) }
.exchange()
verify(exactly = 1) { AuthenticationSuccessHandlerConfig.SUCCESS_HANDLER.onAuthenticationSuccess(any(), any()) }
}
@Configuration
@EnableWebFluxSecurity
@EnableWebFlux
open class AuthenticationSuccessHandlerConfig {
companion object {
val SUCCESS_HANDLER: ServerAuthenticationSuccessHandler = mockk()
}
@Bean
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
return http {
authorizeExchange {
authorize(anyExchange, authenticated)
}
oauth2ResourceServer {
authenticationSuccessHandler = SUCCESS_HANDLER
jwt {
publicKey = publicKey()
}
}
}
}
}
@Test
fun `request when custom bearer token converter configured then custom converter used`() {
this.spring.register(BearerTokenConverterConfig::class.java).autowire()
+5
View File
@@ -8,3 +8,8 @@
== Web
* Since Spring Framework's `HttpMethod#valueOf` now normalizes casing, `StrictServerWebExchangeFirewall` no longer detects a non-canonical-case HTTP method (for example, `get` instead of `GET`) as a distinct value; such requests are processed as the canonical method instead of being rejected. Applications with a customized `ServerExchangeRejectedHandler` should be aware it is no longer invoked for this case.
== OAuth 2.0
* https://github.com/spring-projects/spring-security/pull/18895[gh-18895] - Add `authenticationSuccessHandler` to the Reactive Resource Server DSL
@@ -24,9 +24,12 @@ import java.util.List;
import java.util.Map;
import java.util.Objects;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.jspecify.annotations.Nullable;
import reactor.core.publisher.Mono;
import org.springframework.core.log.LogMessage;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
@@ -79,7 +82,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
.map((c) -> c.get(ServerWebExchange.class));
// @formatter:on
private ServerSecurityContextRepository serverSecurityContextRepository = new WebSessionServerSecurityContextRepository();
private ServerSecurityContextRepository serverSecurityContextRepository = new NonRotatingWebSessionServerSecurityContextRepository();
private ReactiveJwtDecoderFactory<ClientRegistration> jwtDecoderFactory = new ReactiveOidcIdTokenDecoderFactory();
@@ -141,8 +144,7 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
/**
* Sets a {@link ServerSecurityContextRepository} to use for refreshing a
* {@link SecurityContext}, defaults to
* {@link WebSessionServerSecurityContextRepository}.
* {@link SecurityContext}.
* @param serverSecurityContextRepository the {@link ServerSecurityContextRepository}
* to use
*/
@@ -316,4 +318,28 @@ public final class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler
return this.serverSecurityContextRepository.save(exchange, securityContext);
}
private static final class NonRotatingWebSessionServerSecurityContextRepository
implements ServerSecurityContextRepository {
private static final Log logger = LogFactory.getLog(NonRotatingWebSessionServerSecurityContextRepository.class);
@Override
public Mono<SecurityContext> load(ServerWebExchange exchange) {
return Mono.empty();
}
@Override
public Mono<Void> save(ServerWebExchange exchange, @Nullable SecurityContext context) {
Assert.notNull(context, "context cannot be null");
// Save SecurityContext in WebSession without rotating session id.
return exchange.getSession().doOnNext((session) -> {
session.getAttributes()
.put(WebSessionServerSecurityContextRepository.DEFAULT_SPRING_SECURITY_CONTEXT_ATTR_NAME, context);
logger.debug(LogMessage.format("Saved SecurityContext '%s' in WebSession: '%s'", context, session));
}).then();
}
}
}
@@ -50,7 +50,9 @@ import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoderFactory;
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatException;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.BDDMockito.given;
@@ -140,6 +142,51 @@ class RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandlerTests {
.verifyComplete();
}
// gh-19424
@Test
void onAuthorizationSuccessWhenDefaultServerSecurityContextRepositoryThenWebSessionIdNotChanged() {
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
Instant authTime = Instant.now();
DefaultOidcUser principal = createOidcUser(authTime);
OAuth2AuthenticationToken authenticationToken = new OAuth2AuthenticationToken(principal,
principal.getAuthorities(), clientRegistration.getRegistrationId());
OAuth2AccessToken accessToken = createAccessToken();
OAuth2AuthorizedClient authorizedClient = new OAuth2AuthorizedClient(clientRegistration, principal.getName(),
accessToken, null);
MockServerWebExchange exchange = MockServerWebExchange.from(MockServerHttpRequest.get("/").build());
Map<String, Object> attributes = Map.of(ServerWebExchange.class.getName(), exchange,
OidcParameterNames.ID_TOKEN, "id-token-1234");
Map<String, Object> claims = new HashMap<>();
claims.put("iss", principal.getIssuer());
claims.put("sub", principal.getSubject());
claims.put("aud", principal.getAudience());
claims.put("auth_time", authTime);
claims.put("nonce", principal.getNonce());
Jwt jwt = mock(Jwt.class);
given(jwt.getTokenValue()).willReturn("id-token-1234");
given(jwt.getIssuedAt()).willReturn(principal.getIssuedAt());
given(jwt.getClaims()).willReturn(claims);
ReactiveJwtDecoder jwtDecoder = mock(ReactiveJwtDecoder.class);
given(jwtDecoder.decode(any())).willReturn(Mono.just(jwt));
ReactiveJwtDecoderFactory<ClientRegistration> reactiveJwtDecoderFactory = mock(ReactiveJwtDecoderFactory.class);
given(reactiveJwtDecoderFactory.createDecoder(any())).willReturn(jwtDecoder);
ReactiveOAuth2UserService<OidcUserRequest, OidcUser> userService = mock(ReactiveOAuth2UserService.class);
given(userService.loadUser(any())).willReturn(Mono.just(principal));
RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler handler = new RefreshOidcUserReactiveOAuth2AuthorizationSuccessHandler();
handler.setJwtDecoderFactory(reactiveJwtDecoderFactory);
handler.setUserService(userService);
String originalSessionId = exchange.getSession().map(WebSession::getId).block();
StepVerifier.create(handler.onAuthorizationSuccess(authorizedClient, authenticationToken, attributes))
.verifyComplete();
StepVerifier.create(exchange.getSession())
.assertNext((session) -> assertThat(session.getId()).isEqualTo(originalSessionId))
.verifyComplete();
WebSessionServerSecurityContextRepository securityContextRepository = new WebSessionServerSecurityContextRepository();
StepVerifier.create(securityContextRepository.load(exchange).mapNotNull(SecurityContext::getAuthentication))
.expectNext(authenticationToken)
.verifyComplete();
}
@Test
void onAuthorizationSuccessWhenIdTokenIssuerNotSameThenException() {
ClientRegistration clientRegistration = TestClientRegistrations.clientRegistration().build();
@@ -35,6 +35,7 @@ import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.mockito.ArgumentCaptor;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
import reactor.util.context.Context;
import org.springframework.http.HttpHeaders;
@@ -77,6 +78,7 @@ import org.springframework.web.reactive.function.client.ExchangeFunction;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClientResponseException;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebSession;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
@@ -320,6 +322,8 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionITests {
doReturn(Mono.just(authorizedClient)).when(this.authorizedClientRepository)
.loadAuthorizedClient(eq(clientRegistration.getRegistrationId()), eq(this.authentication),
eq(this.exchange));
// Capture the original session id.
String originalSessionId = this.exchange.getSession().map(WebSession::getId).block();
this.webClient.get()
.uri(this.serverUrl)
.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction
@@ -356,6 +360,10 @@ public class ServerOAuth2AuthorizedClientExchangeFilterFunctionITests {
assertThat(oidcUser.getSubject()).isEqualTo("subject-1234");
assertThat(oidcUser.getName()).isEqualTo("refreshed-username");
});
// Verify that session id was not changed.
StepVerifier.create(this.exchange.getSession())
.assertNext((session) -> assertThat(session.getId()).isEqualTo(originalSessionId))
.verifyComplete();
}
@Test