Skip to content

Commit 3329884

Browse files
iain-hendersonjzheaux
authored andcommitted
Add authenticationSuccessHandler to Reactive Resource Server DSL
Signed-off-by: Iain Henderson <Iain.henderson@mac.com>
1 parent 668ef4b commit 3329884

File tree

2 files changed

+205
-0
lines changed

2 files changed

+205
-0
lines changed

config/src/main/java/org/springframework/security/config/web/server/ServerHttpSecurity.java

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -297,6 +297,7 @@
297297
* @author Ankur Pathak
298298
* @author Alexey Nesterov
299299
* @author Yanming Zhou
300+
* @author Iain Henderson
300301
* @since 5.0
301302
*/
302303
public class ServerHttpSecurity {
@@ -4138,6 +4139,8 @@ public class OAuth2ResourceServerSpec {
41384139

41394140
private ServerAuthenticationFailureHandler authenticationFailureHandler;
41404141

4142+
private ServerAuthenticationSuccessHandler authenticationSuccessHandler;
4143+
41414144
private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler();
41424145

41434146
private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter();
@@ -4186,6 +4189,20 @@ public OAuth2ResourceServerSpec authenticationFailureHandler(
41864189
return this;
41874190
}
41884191

4192+
/**
4193+
* Configures the {@link ServerAuthenticationSuccessHandler} to use. The default
4194+
* is {@link WebFilterChainServerAuthenticationSuccessHandler}
4195+
* @param authenticationSuccessHandler the
4196+
* {@link ServerAuthenticationSuccessHandler} to use
4197+
* @return the {@link OAuth2ClientSpec} to customize
4198+
* @since 7.1
4199+
*/
4200+
public OAuth2ResourceServerSpec authenticationSuccessHandler(
4201+
ServerAuthenticationSuccessHandler authenticationSuccessHandler) {
4202+
this.authenticationSuccessHandler = authenticationSuccessHandler;
4203+
return this;
4204+
}
4205+
41894206
/**
41904207
* Configures the {@link ServerAuthenticationConverter} to use for requests
41914208
* authenticating with
@@ -4254,6 +4271,7 @@ protected void configure(ServerHttpSecurity http) {
42544271
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(this.authenticationManagerResolver);
42554272
oauth2.setServerAuthenticationConverter(this.bearerTokenConverter);
42564273
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
4274+
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
42574275
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
42584276
}
42594277
else if (this.jwt != null) {
@@ -4313,6 +4331,13 @@ private ServerAuthenticationFailureHandler authenticationFailureHandler() {
43134331
return new ServerAuthenticationEntryPointFailureHandler(this.entryPoint);
43144332
}
43154333

4334+
private ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
4335+
if (this.authenticationSuccessHandler != null) {
4336+
return this.authenticationSuccessHandler;
4337+
}
4338+
return new WebFilterChainServerAuthenticationSuccessHandler();
4339+
}
4340+
43164341
/**
43174342
* Configures JWT Resource Server Support
43184343
*/
@@ -4387,6 +4412,7 @@ protected void configure(ServerHttpSecurity http) {
43874412
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
43884413
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
43894414
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
4415+
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
43904416
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
43914417
}
43924418

@@ -4519,6 +4545,7 @@ protected void configure(ServerHttpSecurity http) {
45194545
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
45204546
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
45214547
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
4548+
oauth2.setAuthenticationSuccessHandler(authenticationSuccessHandler());
45224549
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
45234550
}
45244551

config/src/test/java/org/springframework/security/config/web/server/OAuth2ResourceServerSpecTests.java

Lines changed: 178 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -73,9 +73,11 @@
7373
import org.springframework.security.oauth2.server.resource.authentication.ReactiveJwtAuthenticationConverterAdapter;
7474
import org.springframework.security.oauth2.server.resource.introspection.ReactiveOpaqueTokenAuthenticationConverter;
7575
import org.springframework.security.web.server.SecurityWebFilterChain;
76+
import org.springframework.security.web.server.WebFilterExchange;
7677
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
7778
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
7879
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
80+
import org.springframework.security.web.server.authentication.ServerAuthenticationSuccessHandler;
7981
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
8082
import org.springframework.test.web.reactive.server.WebTestClient;
8183
import org.springframework.web.bind.annotation.GetMapping;
@@ -371,6 +373,78 @@ public void getWhenUsingCustomAuthenticationFailureHandlerThenUsesIsAccordingly(
371373
verify(handler).onAuthenticationFailure(any(), any());
372374
}
373375

376+
@Test
377+
public void getWhenUsingCustomAuthenticationSuccessHandlerThenUsesIsAccordingly() {
378+
this.spring.register(CustomAuthenticationSuccessHandlerAuthenticationManagerResolverConfig.class).autowire();
379+
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
380+
.getBean(ServerAuthenticationSuccessHandler.class);
381+
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
382+
.getBean(ReactiveAuthenticationManager.class);
383+
given(authenticationManager.authenticate(any()))
384+
.willAnswer(input -> Mono.just(input.getArgument(0, Authentication.class)));
385+
given(handler.onAuthenticationSuccess(any(), any())).willAnswer(input -> {
386+
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
387+
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
388+
});
389+
// @formatter:off
390+
this.client.get()
391+
.headers((headers) -> headers.setBearerAuth(this.messageReadToken))
392+
.exchange()
393+
.expectStatus().isUnauthorized();
394+
// @formatter:on
395+
verify(handler).onAuthenticationSuccess(any(), any());
396+
}
397+
398+
@Test
399+
public void getWhenUsingCustomAuthenticationSuccessHandlerWithJwtThenUsesIsAccordingly() {
400+
this.spring.register(CustomAuthenticationSuccessHandlerJwtConfig.class).autowire();
401+
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
402+
.getBean(ServerAuthenticationSuccessHandler.class);
403+
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
404+
.getBean(ReactiveAuthenticationManager.class);
405+
given(authenticationManager.authenticate(any()))
406+
.willAnswer(input -> Mono.just(input.getArgument(0, Authentication.class)));
407+
given(handler.onAuthenticationSuccess(any(), any())).willAnswer(input -> {
408+
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
409+
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
410+
});
411+
// @formatter:off
412+
this.client.get()
413+
.headers((headers) -> headers.setBearerAuth(this.messageReadToken))
414+
.exchange()
415+
.expectStatus().isUnauthorized();
416+
// @formatter:on
417+
verify(handler).onAuthenticationSuccess(any(), any());
418+
}
419+
420+
@Test
421+
public void getWhenUsingCustomAuthenticationSuccessHandlerWIthOpaqueTokenThenUsesIsAccordingly() {
422+
this.spring.register(CustomAuthenticationSuccessHandlerOpaqueTokenConfig.class, RootController.class).autowire();
423+
this.spring.getContext()
424+
.getBean(MockWebServer.class)
425+
.setDispatcher(requiresAuth(this.clientId, this.clientSecret, this.active));
426+
ServerAuthenticationSuccessHandler handler = this.spring.getContext()
427+
.getBean(ServerAuthenticationSuccessHandler.class);
428+
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
429+
.getBean(ReactiveAuthenticationManager.class);
430+
given(authenticationManager.authenticate(any()))
431+
.willAnswer(input -> Mono.just(input.getArgument(0, Authentication.class)));
432+
given(handler.onAuthenticationSuccess(any(), any())).willAnswer(input -> {
433+
WebFilterExchange webFilterExchange = input.getArgument(0, WebFilterExchange.class);
434+
return webFilterExchange.getChain().filter(webFilterExchange.getExchange());
435+
});
436+
// @formatter:off
437+
this.client.get()
438+
.headers((headers) -> headers
439+
.setBearerAuth(this.messageReadToken)
440+
)
441+
.exchange()
442+
.expectStatus().isOk();
443+
// @formatter:on
444+
445+
verify(handler).onAuthenticationSuccess(any(), any());
446+
}
447+
374448
@Test
375449
public void postWhenSignedThenReturnsOk() {
376450
this.spring.register(PublicKeyConfig.class, RootController.class).autowire();
@@ -950,6 +1024,110 @@ ServerAuthenticationFailureHandler authenticationFailureHandler() {
9501024

9511025
}
9521026

1027+
@Configuration
1028+
@EnableWebFlux
1029+
@EnableWebFluxSecurity
1030+
static class CustomAuthenticationSuccessHandlerAuthenticationManagerResolverConfig {
1031+
1032+
@Bean
1033+
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
1034+
// @formatter:off
1035+
http
1036+
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
1037+
.oauth2ResourceServer((oauth2) -> oauth2
1038+
.authenticationSuccessHandler(authenticationSuccessHandler())
1039+
.authenticationManagerResolver(exchange -> Mono.just(authenticationManager()))
1040+
);
1041+
// @formatter:on
1042+
return http.build();
1043+
}
1044+
1045+
@Bean
1046+
ReactiveAuthenticationManager authenticationManager() {
1047+
return mock(ReactiveAuthenticationManager.class);
1048+
}
1049+
1050+
@Bean
1051+
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
1052+
return mock(ServerAuthenticationSuccessHandler.class);
1053+
}
1054+
1055+
}
1056+
1057+
@Configuration
1058+
@EnableWebFlux
1059+
@EnableWebFluxSecurity
1060+
static class CustomAuthenticationSuccessHandlerJwtConfig {
1061+
1062+
@Bean
1063+
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
1064+
// @formatter:off
1065+
http
1066+
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
1067+
.oauth2ResourceServer((oauth2) -> oauth2
1068+
.authenticationSuccessHandler(authenticationSuccessHandler())
1069+
.jwt((jwt) -> jwt.authenticationManager(authenticationManager()))
1070+
);
1071+
// @formatter:on
1072+
return http.build();
1073+
}
1074+
1075+
@Bean
1076+
ReactiveAuthenticationManager authenticationManager() {
1077+
return mock(ReactiveAuthenticationManager.class);
1078+
}
1079+
1080+
@Bean
1081+
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
1082+
return mock(ServerAuthenticationSuccessHandler.class);
1083+
}
1084+
1085+
}
1086+
1087+
@Configuration
1088+
@EnableWebFlux
1089+
@EnableWebFluxSecurity
1090+
static class CustomAuthenticationSuccessHandlerOpaqueTokenConfig {
1091+
1092+
private MockWebServer mockWebServer = new MockWebServer();
1093+
1094+
@Bean
1095+
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
1096+
String introspectionUri = mockWebServer().url("/introspect").toString();
1097+
// @formatter:off
1098+
http
1099+
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
1100+
.oauth2ResourceServer((oauth2) -> oauth2
1101+
.authenticationSuccessHandler(authenticationSuccessHandler())
1102+
.opaqueToken((opaqueToken) -> opaqueToken
1103+
.introspectionUri(introspectionUri)
1104+
.introspectionClientCredentials("client", "secret"))
1105+
);
1106+
// @formatter:on
1107+
return http.build();
1108+
}
1109+
1110+
@Bean
1111+
ReactiveAuthenticationManager authenticationManager() {
1112+
return mock(ReactiveAuthenticationManager.class);
1113+
}
1114+
1115+
@Bean
1116+
ServerAuthenticationSuccessHandler authenticationSuccessHandler() {
1117+
return mock(ServerAuthenticationSuccessHandler.class);
1118+
}
1119+
1120+
@Bean
1121+
MockWebServer mockWebServer() {
1122+
return this.mockWebServer;
1123+
}
1124+
1125+
@PreDestroy
1126+
void shutdown() throws IOException {
1127+
this.mockWebServer.shutdown();
1128+
}
1129+
}
1130+
9531131
@EnableWebFlux
9541132
@EnableWebFluxSecurity
9551133
static class CustomBearerTokenServerAuthenticationConverter {

0 commit comments

Comments
 (0)