Skip to content

Commit 3192618

Browse files
committed
Add authenticationFailureHandler
- To ServerHttpSecurity#httpBasic - To ServerHttpSecurity#oauthResourceServer Closes gh-12132
1 parent 6622e01 commit 3192618

File tree

7 files changed

+199
-13
lines changed

7 files changed

+199
-13
lines changed

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

Lines changed: 35 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2023,6 +2023,8 @@ public final class HttpBasicSpec {
20232023

20242024
private ServerAuthenticationEntryPoint entryPoint;
20252025

2026+
private ServerAuthenticationFailureHandler authenticationFailureHandler;
2027+
20262028
private HttpBasicSpec() {
20272029
List<DelegateEntry> entryPoints = new ArrayList<>();
20282030
entryPoints
@@ -2071,6 +2073,13 @@ public HttpBasicSpec authenticationEntryPoint(ServerAuthenticationEntryPoint aut
20712073
return this;
20722074
}
20732075

2076+
public HttpBasicSpec authenticationFailureHandler(
2077+
ServerAuthenticationFailureHandler authenticationFailureHandler) {
2078+
Assert.notNull(authenticationFailureHandler, "authenticationFailureHandler cannot be null");
2079+
this.authenticationFailureHandler = authenticationFailureHandler;
2080+
return this;
2081+
}
2082+
20742083
/**
20752084
* Allows method chaining to continue configuring the {@link ServerHttpSecurity}
20762085
* @return the {@link ServerHttpSecurity} to continue configuring
@@ -2102,13 +2111,19 @@ protected void configure(ServerHttpSecurity http) {
21022111
Arrays.asList(this.xhrMatcher, restNotHtmlMatcher));
21032112
ServerHttpSecurity.this.defaultEntryPoints.add(new DelegateEntry(preferredMatcher, this.entryPoint));
21042113
AuthenticationWebFilter authenticationFilter = new AuthenticationWebFilter(this.authenticationManager);
2105-
authenticationFilter
2106-
.setAuthenticationFailureHandler(new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));
2114+
authenticationFilter.setAuthenticationFailureHandler(authenticationFailureHandler());
21072115
authenticationFilter.setAuthenticationConverter(new ServerHttpBasicAuthenticationConverter());
21082116
authenticationFilter.setSecurityContextRepository(this.securityContextRepository);
21092117
http.addFilterAt(authenticationFilter, SecurityWebFiltersOrder.HTTP_BASIC);
21102118
}
21112119

2120+
private ServerAuthenticationFailureHandler authenticationFailureHandler() {
2121+
if (this.authenticationFailureHandler != null) {
2122+
return this.authenticationFailureHandler;
2123+
}
2124+
return new ServerAuthenticationEntryPointFailureHandler(this.entryPoint);
2125+
}
2126+
21122127
}
21132128

21142129
/**
@@ -3996,6 +4011,8 @@ public class OAuth2ResourceServerSpec {
39964011

39974012
private ServerAuthenticationEntryPoint entryPoint = new BearerTokenServerAuthenticationEntryPoint();
39984013

4014+
private ServerAuthenticationFailureHandler authenticationFailureHandler;
4015+
39994016
private ServerAccessDeniedHandler accessDeniedHandler = new BearerTokenServerAccessDeniedHandler();
40004017

40014018
private ServerAuthenticationConverter bearerTokenConverter = new ServerBearerTokenAuthenticationConverter();
@@ -4038,6 +4055,12 @@ public OAuth2ResourceServerSpec authenticationEntryPoint(ServerAuthenticationEnt
40384055
return this;
40394056
}
40404057

4058+
public OAuth2ResourceServerSpec authenticationFailureHandler(
4059+
ServerAuthenticationFailureHandler authenticationFailureHandler) {
4060+
this.authenticationFailureHandler = authenticationFailureHandler;
4061+
return this;
4062+
}
4063+
40414064
/**
40424065
* Configures the {@link ServerAuthenticationConverter} to use for requests
40434066
* authenticating with
@@ -4127,8 +4150,7 @@ protected void configure(ServerHttpSecurity http) {
41274150
if (this.authenticationManagerResolver != null) {
41284151
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(this.authenticationManagerResolver);
41294152
oauth2.setServerAuthenticationConverter(this.bearerTokenConverter);
4130-
oauth2.setAuthenticationFailureHandler(
4131-
new ServerAuthenticationEntryPointFailureHandler(this.entryPoint));
4153+
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
41324154
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
41334155
}
41344156
else if (this.jwt != null) {
@@ -4181,6 +4203,13 @@ private void registerDefaultCsrfOverride(ServerHttpSecurity http) {
41814203
}
41824204
}
41834205

4206+
private ServerAuthenticationFailureHandler authenticationFailureHandler() {
4207+
if (this.authenticationFailureHandler != null) {
4208+
return this.authenticationFailureHandler;
4209+
}
4210+
return new ServerAuthenticationEntryPointFailureHandler(this.entryPoint);
4211+
}
4212+
41844213
public ServerHttpSecurity and() {
41854214
return ServerHttpSecurity.this;
41864215
}
@@ -4262,8 +4291,7 @@ protected void configure(ServerHttpSecurity http) {
42624291
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
42634292
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
42644293
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
4265-
oauth2.setAuthenticationFailureHandler(
4266-
new ServerAuthenticationEntryPointFailureHandler(OAuth2ResourceServerSpec.this.entryPoint));
4294+
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
42674295
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
42684296
}
42694297

@@ -4398,8 +4426,7 @@ protected void configure(ServerHttpSecurity http) {
43984426
ReactiveAuthenticationManager authenticationManager = getAuthenticationManager();
43994427
AuthenticationWebFilter oauth2 = new AuthenticationWebFilter(authenticationManager);
44004428
oauth2.setServerAuthenticationConverter(OAuth2ResourceServerSpec.this.bearerTokenConverter);
4401-
oauth2.setAuthenticationFailureHandler(
4402-
new ServerAuthenticationEntryPointFailureHandler(OAuth2ResourceServerSpec.this.entryPoint));
4429+
oauth2.setAuthenticationFailureHandler(authenticationFailureHandler());
44034430
http.addFilterAt(oauth2, SecurityWebFiltersOrder.AUTHENTICATION);
44044431
}
44054432

config/src/main/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDsl.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,7 @@ import org.springframework.security.core.Authentication
2121
import org.springframework.security.core.context.SecurityContext
2222
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter
2323
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
24+
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
2425
import org.springframework.security.web.server.context.ReactorContextWebFilter
2526
import org.springframework.security.web.server.context.ServerSecurityContextRepository
2627

@@ -42,6 +43,7 @@ import org.springframework.security.web.server.context.ServerSecurityContextRepo
4243
class ServerHttpBasicDsl {
4344
var authenticationManager: ReactiveAuthenticationManager? = null
4445
var securityContextRepository: ServerSecurityContextRepository? = null
46+
var authenticationFailureHandler: ServerAuthenticationFailureHandler? = null
4547
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
4648

4749
private var disabled = false
@@ -57,6 +59,7 @@ class ServerHttpBasicDsl {
5759
return { httpBasic ->
5860
authenticationManager?.also { httpBasic.authenticationManager(authenticationManager) }
5961
securityContextRepository?.also { httpBasic.securityContextRepository(securityContextRepository) }
62+
authenticationFailureHandler?.also { httpBasic.authenticationFailureHandler(authenticationFailureHandler) }
6063
authenticationEntryPoint?.also { httpBasic.authenticationEntryPoint(authenticationEntryPoint) }
6164
if (disabled) {
6265
httpBasic.disable()

config/src/main/kotlin/org/springframework/security/config/web/server/ServerOAuth2ResourceServerDsl.kt

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,7 @@ package org.springframework.security.config.web.server
1919
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver
2020
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
2121
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter
22+
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
2223
import org.springframework.security.web.server.authorization.ServerAccessDeniedHandler
2324
import org.springframework.web.server.ServerWebExchange
2425

@@ -38,6 +39,7 @@ import org.springframework.web.server.ServerWebExchange
3839
@ServerSecurityMarker
3940
class ServerOAuth2ResourceServerDsl {
4041
var accessDeniedHandler: ServerAccessDeniedHandler? = null
42+
var authenticationFailureHandler: ServerAuthenticationFailureHandler? = null
4143
var authenticationEntryPoint: ServerAuthenticationEntryPoint? = null
4244
var bearerTokenConverter: ServerAuthenticationConverter? = null
4345
var authenticationManagerResolver: ReactiveAuthenticationManagerResolver<ServerWebExchange>? = null
@@ -107,6 +109,7 @@ class ServerOAuth2ResourceServerDsl {
107109
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec) -> Unit {
108110
return { oauth2ResourceServer ->
109111
accessDeniedHandler?.also { oauth2ResourceServer.accessDeniedHandler(accessDeniedHandler) }
112+
authenticationFailureHandler?.also { oauth2ResourceServer.authenticationFailureHandler(authenticationFailureHandler) }
110113
authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
111114
bearerTokenConverter?.also { oauth2ResourceServer.bearerTokenConverter(bearerTokenConverter) }
112115
authenticationManagerResolver?.also { oauth2ResourceServer.authenticationManagerResolver(authenticationManagerResolver!!) }

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

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,7 @@
5151
import org.springframework.http.HttpStatus;
5252
import org.springframework.http.MediaType;
5353
import org.springframework.security.authentication.AbstractAuthenticationToken;
54+
import org.springframework.security.authentication.BadCredentialsException;
5455
import org.springframework.security.authentication.ReactiveAuthenticationManager;
5556
import org.springframework.security.authentication.ReactiveAuthenticationManagerResolver;
5657
import org.springframework.security.authentication.TestingAuthenticationToken;
@@ -73,6 +74,7 @@
7374
import org.springframework.security.web.server.SecurityWebFilterChain;
7475
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
7576
import org.springframework.security.web.server.authentication.ServerAuthenticationConverter;
77+
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
7678
import org.springframework.security.web.server.authorization.HttpStatusServerAccessDeniedHandler;
7779
import org.springframework.test.context.junit.jupiter.SpringExtension;
7880
import org.springframework.test.web.reactive.server.WebTestClient;
@@ -348,6 +350,25 @@ public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly
348350
// @formatter:on
349351
}
350352

353+
@Test
354+
public void getWhenUsingCustomAuthenticationFailureHandlerThenUsesIsAccordingly() {
355+
this.spring.register(CustomAuthenticationFailureHandlerConfig.class).autowire();
356+
ServerAuthenticationFailureHandler handler = this.spring.getContext()
357+
.getBean(ServerAuthenticationFailureHandler.class);
358+
ReactiveAuthenticationManager authenticationManager = this.spring.getContext()
359+
.getBean(ReactiveAuthenticationManager.class);
360+
given(authenticationManager.authenticate(any()))
361+
.willReturn(Mono.error(() -> new BadCredentialsException("bad")));
362+
given(handler.onAuthenticationFailure(any(), any())).willReturn(Mono.empty());
363+
// @formatter:off
364+
this.client.get()
365+
.headers((headers) -> headers.setBearerAuth(this.messageReadToken))
366+
.exchange()
367+
.expectStatus().isOk();
368+
// @formatter:on
369+
verify(handler).onAuthenticationFailure(any(), any());
370+
}
371+
351372
@Test
352373
public void postWhenSignedThenReturnsOk() {
353374
this.spring.register(PublicKeyConfig.class, RootController.class).autowire();
@@ -893,6 +914,35 @@ ReactiveAuthenticationManager authenticationManager() {
893914

894915
}
895916

917+
@EnableWebFlux
918+
@EnableWebFluxSecurity
919+
static class CustomAuthenticationFailureHandlerConfig {
920+
921+
@Bean
922+
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
923+
// @formatter:off
924+
http
925+
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
926+
.oauth2ResourceServer((oauth2) -> oauth2
927+
.authenticationFailureHandler(authenticationFailureHandler())
928+
.jwt((jwt) -> jwt.authenticationManager(authenticationManager()))
929+
);
930+
// @formatter:on
931+
return http.build();
932+
}
933+
934+
@Bean
935+
ReactiveAuthenticationManager authenticationManager() {
936+
return mock(ReactiveAuthenticationManager.class);
937+
}
938+
939+
@Bean
940+
ServerAuthenticationFailureHandler authenticationFailureHandler() {
941+
return mock(ServerAuthenticationFailureHandler.class);
942+
}
943+
944+
}
945+
896946
@EnableWebFlux
897947
@EnableWebFluxSecurity
898948
static class CustomBearerTokenServerAuthenticationConverter {

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

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,6 +35,7 @@
3535
import org.springframework.http.HttpStatus;
3636
import org.springframework.mock.http.server.reactive.MockServerHttpRequest;
3737
import org.springframework.mock.web.server.MockServerWebExchange;
38+
import org.springframework.security.authentication.BadCredentialsException;
3839
import org.springframework.security.authentication.ReactiveAuthenticationManager;
3940
import org.springframework.security.authentication.TestingAuthenticationToken;
4041
import org.springframework.security.config.annotation.web.reactive.ServerHttpSecurityConfigurationBuilder;
@@ -57,6 +58,7 @@
5758
import org.springframework.security.web.server.authentication.AnonymousAuthenticationWebFilterTests;
5859
import org.springframework.security.web.server.authentication.HttpBasicServerAuthenticationEntryPoint;
5960
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint;
61+
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler;
6062
import org.springframework.security.web.server.authentication.ServerX509AuthenticationConverter;
6163
import org.springframework.security.web.server.authentication.logout.DelegatingServerLogoutHandler;
6264
import org.springframework.security.web.server.authentication.logout.LogoutWebFilter;
@@ -218,6 +220,27 @@ public void basicWhenXHRRequestThenUnauthorized() {
218220
verify(authenticationEntryPoint).commence(any(), any());
219221
}
220222

223+
@Test
224+
public void basicWhenCustomAuthenticationFailureHandlerThenUses() {
225+
ReactiveAuthenticationManager authenticationManager = mock(ReactiveAuthenticationManager.class);
226+
ServerAuthenticationFailureHandler authenticationFailureHandler = mock(
227+
ServerAuthenticationFailureHandler.class);
228+
this.http.httpBasic().authenticationFailureHandler(authenticationFailureHandler);
229+
this.http.httpBasic().authenticationManager(authenticationManager);
230+
this.http.authorizeExchange().anyExchange().authenticated();
231+
given(authenticationManager.authenticate(any()))
232+
.willReturn(Mono.error(() -> new BadCredentialsException("bad")));
233+
given(authenticationFailureHandler.onAuthenticationFailure(any(), any())).willReturn(Mono.empty());
234+
WebTestClient client = buildClient();
235+
// @formatter:off
236+
client.get().uri("/")
237+
.headers((headers) -> headers.setBasicAuth("user", "password"))
238+
.exchange()
239+
.expectStatus().isOk();
240+
// @formatter:on
241+
verify(authenticationFailureHandler).onAuthenticationFailure(any(), any());
242+
}
243+
221244
@Test
222245
public void buildWhenServerWebExchangeFromContextThenFound() {
223246
SecurityWebFilterChain filter = this.http.build();

config/src/test/kotlin/org/springframework/security/config/web/server/ServerHttpBasicDslTests.kt

Lines changed: 39 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,6 @@ package org.springframework.security.config.web.server
1919
import io.mockk.every
2020
import io.mockk.mockkObject
2121
import io.mockk.verify
22-
import java.util.Base64
2322
import org.junit.jupiter.api.Test
2423
import org.junit.jupiter.api.extension.ExtendWith
2524
import org.springframework.beans.factory.annotation.Autowired
@@ -36,13 +35,15 @@ import org.springframework.security.core.userdetails.MapReactiveUserDetailsServi
3635
import org.springframework.security.core.userdetails.User
3736
import org.springframework.security.web.server.SecurityWebFilterChain
3837
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
38+
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
3939
import org.springframework.security.web.server.context.ServerSecurityContextRepository
4040
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository
4141
import org.springframework.test.web.reactive.server.WebTestClient
4242
import org.springframework.web.bind.annotation.RequestMapping
4343
import org.springframework.web.bind.annotation.RestController
4444
import org.springframework.web.reactive.config.EnableWebFlux
4545
import reactor.core.publisher.Mono
46+
import java.util.*
4647

4748
/**
4849
* Tests for [ServerHttpBasicDsl]
@@ -216,6 +217,43 @@ class ServerHttpBasicDslTests {
216217
}
217218
}
218219

220+
@Test
221+
fun `http basic when custom authentication failure handler then failure handler used`() {
222+
this.spring.register(CustomAuthenticationFailureHandlerConfig::class.java, UserDetailsConfig::class.java).autowire()
223+
mockkObject(CustomAuthenticationFailureHandlerConfig.FAILURE_HANDLER)
224+
every {
225+
CustomAuthenticationFailureHandlerConfig.FAILURE_HANDLER.onAuthenticationFailure(any(), any())
226+
} returns Mono.empty()
227+
228+
this.client.get()
229+
.uri("/")
230+
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:wrong".toByteArray()))
231+
.exchange()
232+
233+
verify(exactly = 1) { CustomAuthenticationFailureHandlerConfig.FAILURE_HANDLER.onAuthenticationFailure(any(), any()) }
234+
}
235+
236+
@EnableWebFluxSecurity
237+
@EnableWebFlux
238+
open class CustomAuthenticationFailureHandlerConfig {
239+
240+
companion object {
241+
val FAILURE_HANDLER: ServerAuthenticationFailureHandler = ServerAuthenticationFailureHandler { _, _ -> Mono.empty() }
242+
}
243+
244+
@Bean
245+
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
246+
return http {
247+
authorizeExchange {
248+
authorize(anyExchange, authenticated)
249+
}
250+
httpBasic {
251+
authenticationFailureHandler = FAILURE_HANDLER
252+
}
253+
}
254+
}
255+
}
256+
219257
@Configuration
220258
open class UserDetailsConfig {
221259
@Bean

0 commit comments

Comments
 (0)