diff --git a/etc/checkstyle/checkstyle-suppressions.xml b/etc/checkstyle/checkstyle-suppressions.xml index a4566eff53..74b7de0b19 100644 --- a/etc/checkstyle/checkstyle-suppressions.xml +++ b/etc/checkstyle/checkstyle-suppressions.xml @@ -37,4 +37,7 @@ + + + diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java index 5fce70fb34..36bada5462 100644 --- a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java +++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java @@ -36,12 +36,14 @@ import org.springframework.security.authentication.AuthenticationManager; import org.springframework.security.authentication.AuthenticationManagerResolver; import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.TestingAuthenticationToken; +import org.springframework.security.core.AuthenticationException; import org.springframework.security.core.context.SecurityContext; import org.springframework.security.core.context.SecurityContextHolderStrategy; import org.springframework.security.core.context.SecurityContextImpl; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.server.resource.BearerTokenError; import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes; +import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.web.AuthenticationEntryPoint; @@ -203,6 +205,19 @@ public class BearerTokenAuthenticationFilterTests { .isThrownBy(() -> filter.doFilter(this.request, this.response, this.filterChain)); } + @Test + public void doFilterWhenCustomEntryPointAndAuthenticationErrorThenUses() throws ServletException, IOException { + AuthenticationException exception = new InvalidBearerTokenException("message"); + given(this.bearerTokenResolver.resolve(this.request)).willReturn("token"); + given(this.authenticationManager.authenticate(any())).willThrow(exception); + BearerTokenAuthenticationFilter filter = addMocks( + new BearerTokenAuthenticationFilter(this.authenticationManager)); + AuthenticationEntryPoint entrypoint = mock(AuthenticationEntryPoint.class); + filter.setAuthenticationEntryPoint(entrypoint); + filter.doFilter(this.request, this.response, this.filterChain); + verify(entrypoint).commence(any(), any(), any(InvalidBearerTokenException.class)); + } + @Test public void doFilterWhenCustomAuthenticationDetailsSourceThenUses() throws ServletException, IOException { given(this.bearerTokenResolver.resolve(this.request)).willReturn("token"); diff --git a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandler.java b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandler.java index 0e1b8fbbc0..e42cfe6a49 100644 --- a/web/src/main/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandler.java +++ b/web/src/main/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2019 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -22,6 +22,7 @@ import jakarta.servlet.ServletException; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; +import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.AuthenticationEntryPoint; import org.springframework.util.Assert; @@ -34,6 +35,8 @@ import org.springframework.util.Assert; */ public class AuthenticationEntryPointFailureHandler implements AuthenticationFailureHandler { + private boolean rethrowAuthenticationServiceException = false; + private final AuthenticationEntryPoint authenticationEntryPoint; public AuthenticationEntryPointFailureHandler(AuthenticationEntryPoint authenticationEntryPoint) { @@ -44,7 +47,25 @@ public class AuthenticationEntryPointFailureHandler implements AuthenticationFai @Override public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, AuthenticationException exception) throws IOException, ServletException { - this.authenticationEntryPoint.commence(request, response, exception); + if (!this.rethrowAuthenticationServiceException) { + this.authenticationEntryPoint.commence(request, response, exception); + return; + } + if (!AuthenticationServiceException.class.isAssignableFrom(exception.getClass())) { + this.authenticationEntryPoint.commence(request, response, exception); + return; + } + throw exception; + } + + /** + * Set whether to rethrow {@link AuthenticationServiceException}s (defaults to false) + * @param rethrowAuthenticationServiceException whether to rethrow + * {@link AuthenticationServiceException}s + * @since 5.8 + */ + public void setRethrowAuthenticationServiceException(boolean rethrowAuthenticationServiceException) { + this.rethrowAuthenticationServiceException = rethrowAuthenticationServiceException; } } diff --git a/web/src/main/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandler.java b/web/src/main/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandler.java index 04061d91f9..74bcc1dbb2 100644 --- a/web/src/main/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandler.java +++ b/web/src/main/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandler.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2017 the original author or authors. + * Copyright 2002-2022 the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ package org.springframework.security.web.server.authentication; import reactor.core.publisher.Mono; +import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.core.AuthenticationException; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.WebFilterExchange; @@ -34,6 +35,8 @@ public class ServerAuthenticationEntryPointFailureHandler implements ServerAuthe private final ServerAuthenticationEntryPoint authenticationEntryPoint; + private boolean rethrowAuthenticationServiceException = false; + public ServerAuthenticationEntryPointFailureHandler(ServerAuthenticationEntryPoint authenticationEntryPoint) { Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null"); this.authenticationEntryPoint = authenticationEntryPoint; @@ -41,7 +44,23 @@ public class ServerAuthenticationEntryPointFailureHandler implements ServerAuthe @Override public Mono onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { - return this.authenticationEntryPoint.commence(webFilterExchange.getExchange(), exception); + if (!this.rethrowAuthenticationServiceException) { + return this.authenticationEntryPoint.commence(webFilterExchange.getExchange(), exception); + } + if (!AuthenticationServiceException.class.isAssignableFrom(exception.getClass())) { + return this.authenticationEntryPoint.commence(webFilterExchange.getExchange(), exception); + } + return Mono.error(exception); + } + + /** + * Set whether to rethrow {@link AuthenticationServiceException}s (defaults to false) + * @param rethrowAuthenticationServiceException whether to rethrow + * {@link AuthenticationServiceException}s + * @since 5.8 + */ + public void setRethrowAuthenticationServiceException(boolean rethrowAuthenticationServiceException) { + this.rethrowAuthenticationServiceException = rethrowAuthenticationServiceException; } } diff --git a/web/src/test/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandlerTests.java b/web/src/test/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandlerTests.java new file mode 100644 index 0000000000..af52e705d1 --- /dev/null +++ b/web/src/test/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandlerTests.java @@ -0,0 +1,48 @@ +/* + * Copyright 2002-2022 the original author or authors. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.springframework.security.web.authentication; + +import org.junit.jupiter.api.Test; + +import org.springframework.security.authentication.AuthenticationServiceException; +import org.springframework.security.web.AuthenticationEntryPoint; + +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; +import static org.mockito.Mockito.mock; + +/** + * Tests for {@link AuthenticationEntryPointFailureHandler} + */ +public class AuthenticationEntryPointFailureHandlerTests { + + @Test + void onAuthenticationFailureWhenDefaultsThenAuthenticationServiceExceptionSwallowed() throws Exception { + AuthenticationEntryPoint entryPoint = mock(AuthenticationEntryPoint.class); + AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(entryPoint); + handler.onAuthenticationFailure(null, null, new AuthenticationServiceException("fail")); + } + + @Test + void handleWhenRethrowingThenAuthenticationServiceExceptionRethrown() { + AuthenticationEntryPoint entryPoint = mock(AuthenticationEntryPoint.class); + AuthenticationEntryPointFailureHandler handler = new AuthenticationEntryPointFailureHandler(entryPoint); + handler.setRethrowAuthenticationServiceException(true); + assertThatExceptionOfType(AuthenticationServiceException.class).isThrownBy( + () -> handler.onAuthenticationFailure(null, null, new AuthenticationServiceException("fail"))); + } + +} diff --git a/web/src/test/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandlerTests.java b/web/src/test/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandlerTests.java index 1ee69267a0..670f476c65 100644 --- a/web/src/test/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandlerTests.java +++ b/web/src/test/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandlerTests.java @@ -23,6 +23,7 @@ import org.mockito.Mock; import org.mockito.junit.jupiter.MockitoExtension; import reactor.core.publisher.Mono; +import org.springframework.security.authentication.AuthenticationServiceException; import org.springframework.security.authentication.BadCredentialsException; import org.springframework.security.web.server.ServerAuthenticationEntryPoint; import org.springframework.security.web.server.WebFilterExchange; @@ -30,6 +31,7 @@ import org.springframework.web.server.ServerWebExchange; import org.springframework.web.server.WebFilterChain; import static org.assertj.core.api.Assertions.assertThat; +import static org.assertj.core.api.Assertions.assertThatExceptionOfType; import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException; import static org.mockito.BDDMockito.given; @@ -68,4 +70,19 @@ public class ServerAuthenticationEntryPointFailureHandlerTests { assertThat(this.handler.onAuthenticationFailure(this.filterExchange, e)).isEqualTo(result); } + @Test + void onAuthenticationFailureWhenDefaultsThenAuthenticationServiceExceptionSwallowed() { + AuthenticationServiceException e = new AuthenticationServiceException("fail"); + given(this.authenticationEntryPoint.commence(this.exchange, e)).willReturn(Mono.empty()); + this.handler.onAuthenticationFailure(this.filterExchange, e).block(); + } + + @Test + void handleWhenRethrowingThenAuthenticationServiceExceptionRethrown() { + AuthenticationServiceException e = new AuthenticationServiceException("fail"); + this.handler.setRethrowAuthenticationServiceException(true); + assertThatExceptionOfType(AuthenticationServiceException.class) + .isThrownBy(() -> this.handler.onAuthenticationFailure(this.filterExchange, e).block()); + } + }