Skip to content

Commit fc8e20b

Browse files
committed
Merge branch '5.8.x'
Closes gh-12133
2 parents 983f1d4 + 3192618 commit fc8e20b

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
@@ -109,6 +111,7 @@ class ServerOAuth2ResourceServerDsl {
109111
internal fun get(): (ServerHttpSecurity.OAuth2ResourceServerSpec) -> Unit {
110112
return { oauth2ResourceServer ->
111113
accessDeniedHandler?.also { oauth2ResourceServer.accessDeniedHandler(accessDeniedHandler) }
114+
authenticationFailureHandler?.also { oauth2ResourceServer.authenticationFailureHandler(authenticationFailureHandler) }
112115
authenticationEntryPoint?.also { oauth2ResourceServer.authenticationEntryPoint(authenticationEntryPoint) }
113116
bearerTokenConverter?.also { oauth2ResourceServer.bearerTokenConverter(bearerTokenConverter) }
114117
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.web.reactive.server.WebTestClient;
7880
import org.springframework.web.bind.annotation.GetMapping;
@@ -347,6 +349,25 @@ public void getWhenUsingCustomAuthenticationManagerResolverThenUsesItAccordingly
347349
// @formatter:on
348350
}
349351

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

905926
@Configuration
927+
@EnableWebFlux
928+
@EnableWebFluxSecurity
929+
static class CustomAuthenticationFailureHandlerConfig {
930+
931+
@Bean
932+
SecurityWebFilterChain springSecurity(ServerHttpSecurity http) {
933+
// @formatter:off
934+
http
935+
.authorizeExchange((authorize) -> authorize.anyExchange().authenticated())
936+
.oauth2ResourceServer((oauth2) -> oauth2
937+
.authenticationFailureHandler(authenticationFailureHandler())
938+
.jwt((jwt) -> jwt.authenticationManager(authenticationManager()))
939+
);
940+
// @formatter:on
941+
return http.build();
942+
}
943+
944+
@Bean
945+
ReactiveAuthenticationManager authenticationManager() {
946+
return mock(ReactiveAuthenticationManager.class);
947+
}
948+
949+
@Bean
950+
ServerAuthenticationFailureHandler authenticationFailureHandler() {
951+
return mock(ServerAuthenticationFailureHandler.class);
952+
}
953+
954+
}
955+
906956
@EnableWebFlux
907957
@EnableWebFluxSecurity
908958
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
@@ -38,13 +37,15 @@ import org.springframework.security.core.userdetails.User
3837
import org.springframework.security.web.server.SecurityWebFilterChain
3938
import org.springframework.security.web.server.ServerAuthenticationEntryPoint
4039
import org.springframework.security.web.server.authentication.HttpStatusServerEntryPoint
40+
import org.springframework.security.web.server.authentication.ServerAuthenticationFailureHandler
4141
import org.springframework.security.web.server.context.ServerSecurityContextRepository
4242
import org.springframework.security.web.server.context.WebSessionServerSecurityContextRepository
4343
import org.springframework.test.web.reactive.server.WebTestClient
4444
import org.springframework.web.bind.annotation.RequestMapping
4545
import org.springframework.web.bind.annotation.RestController
4646
import org.springframework.web.reactive.config.EnableWebFlux
4747
import reactor.core.publisher.Mono
48+
import java.util.*
4849

4950
/**
5051
* Tests for [ServerHttpBasicDsl]
@@ -228,6 +229,43 @@ class ServerHttpBasicDslTests {
228229
}
229230
}
230231

232+
@Test
233+
fun `http basic when custom authentication failure handler then failure handler used`() {
234+
this.spring.register(CustomAuthenticationFailureHandlerConfig::class.java, UserDetailsConfig::class.java).autowire()
235+
mockkObject(CustomAuthenticationFailureHandlerConfig.FAILURE_HANDLER)
236+
every {
237+
CustomAuthenticationFailureHandlerConfig.FAILURE_HANDLER.onAuthenticationFailure(any(), any())
238+
} returns Mono.empty()
239+
240+
this.client.get()
241+
.uri("/")
242+
.header("Authorization", "Basic " + Base64.getEncoder().encodeToString("user:wrong".toByteArray()))
243+
.exchange()
244+
245+
verify(exactly = 1) { CustomAuthenticationFailureHandlerConfig.FAILURE_HANDLER.onAuthenticationFailure(any(), any()) }
246+
}
247+
248+
@EnableWebFluxSecurity
249+
@EnableWebFlux
250+
open class CustomAuthenticationFailureHandlerConfig {
251+
252+
companion object {
253+
val FAILURE_HANDLER: ServerAuthenticationFailureHandler = ServerAuthenticationFailureHandler { _, _ -> Mono.empty() }
254+
}
255+
256+
@Bean
257+
open fun springWebFilterChain(http: ServerHttpSecurity): SecurityWebFilterChain {
258+
return http {
259+
authorizeExchange {
260+
authorize(anyExchange, authenticated)
261+
}
262+
httpBasic {
263+
authenticationFailureHandler = FAILURE_HANDLER
264+
}
265+
}
266+
}
267+
}
268+
231269
@Configuration
232270
open class UserDetailsConfig {
233271
@Bean

0 commit comments

Comments
 (0)