Browse Source
Issue gh-11932, gh-9429 (Server)AuthenticationEntryPointFailureHandler should produce HTTP 500 instead when an AuthenticationServiceException is thrown, instead of HTTP 401. This commit deprecates the current behavior and introduces an opt-in (Server)AuthenticationEntryPointFailureHandlerAdapter with the expected behavior. BearerTokenAuthenticationFilter uses the new adapter, but with a closure to keep the current behavior re: entrypoint.pull/12044/head
9 changed files with 289 additions and 9 deletions
@ -0,0 +1,56 @@
@@ -0,0 +1,56 @@
|
||||
/* |
||||
* 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 java.io.IOException; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.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; |
||||
|
||||
/** |
||||
* Adapts a {@link AuthenticationEntryPoint} into a {@link AuthenticationFailureHandler}. |
||||
* When the failure is an {@link AuthenticationServiceException}, it re-throws, to produce |
||||
* an HTTP 500 error. |
||||
* |
||||
* @author Daniel Garnier-Moiroux |
||||
* @since 5.8 |
||||
*/ |
||||
public final class AuthenticationEntryPointFailureHandlerAdapter implements AuthenticationFailureHandler { |
||||
|
||||
private final AuthenticationEntryPoint authenticationEntryPoint; |
||||
|
||||
public AuthenticationEntryPointFailureHandlerAdapter(AuthenticationEntryPoint authenticationEntryPoint) { |
||||
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null"); |
||||
this.authenticationEntryPoint = authenticationEntryPoint; |
||||
} |
||||
|
||||
@Override |
||||
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response, |
||||
AuthenticationException failure) throws IOException, ServletException { |
||||
if (AuthenticationServiceException.class.isAssignableFrom(failure.getClass())) { |
||||
throw failure; |
||||
} |
||||
this.authenticationEntryPoint.commence(request, response, failure); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,53 @@
@@ -0,0 +1,53 @@
|
||||
/* |
||||
* 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.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; |
||||
import org.springframework.util.Assert; |
||||
|
||||
/** |
||||
* Adapts a {@link ServerAuthenticationEntryPoint} into a |
||||
* {@link ServerAuthenticationFailureHandler}. When the failure is an |
||||
* {@link AuthenticationServiceException}, it re-throws, to produce an HTTP 500 error. |
||||
* |
||||
* @author Daniel Garnier-Moiroux |
||||
* @since 5.8 |
||||
*/ |
||||
public class ServerAuthenticationEntryPointFailureHandlerAdapter implements ServerAuthenticationFailureHandler { |
||||
|
||||
private final ServerAuthenticationEntryPoint authenticationEntryPoint; |
||||
|
||||
public ServerAuthenticationEntryPointFailureHandlerAdapter( |
||||
ServerAuthenticationEntryPoint authenticationEntryPoint) { |
||||
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null"); |
||||
this.authenticationEntryPoint = authenticationEntryPoint; |
||||
} |
||||
|
||||
@Override |
||||
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) { |
||||
if (AuthenticationServiceException.class.isAssignableFrom(exception.getClass())) { |
||||
return Mono.error(exception); |
||||
} |
||||
return this.authenticationEntryPoint.commence(webFilterExchange.getExchange(), exception); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,69 @@
@@ -0,0 +1,69 @@
|
||||
/* |
||||
* 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 java.io.IOException; |
||||
|
||||
import javax.servlet.ServletException; |
||||
import javax.servlet.http.HttpServletRequest; |
||||
import javax.servlet.http.HttpServletResponse; |
||||
|
||||
import org.junit.jupiter.api.Test; |
||||
|
||||
import org.springframework.security.authentication.AuthenticationServiceException; |
||||
import org.springframework.security.core.AuthenticationException; |
||||
import org.springframework.security.web.AuthenticationEntryPoint; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
|
||||
/** |
||||
* @author Daniel Garnier-Moiroux |
||||
* @since 5.8 |
||||
*/ |
||||
class AuthenticationEntryPointFailureHandlerAdapterTest { |
||||
|
||||
private final AuthenticationEntryPoint authenticationEntryPoint = mock(AuthenticationEntryPoint.class); |
||||
|
||||
private final HttpServletRequest request = mock(HttpServletRequest.class); |
||||
|
||||
private final HttpServletResponse response = mock(HttpServletResponse.class); |
||||
|
||||
@Test |
||||
void onAuthenticationFailureThenCommenceAuthentication() throws ServletException, IOException { |
||||
AuthenticationEntryPointFailureHandlerAdapter failureHandler = new AuthenticationEntryPointFailureHandlerAdapter( |
||||
this.authenticationEntryPoint); |
||||
AuthenticationException failure = new AuthenticationException("failed") { |
||||
}; |
||||
failureHandler.onAuthenticationFailure(this.request, this.response, failure); |
||||
verify(this.authenticationEntryPoint).commence(this.request, this.response, failure); |
||||
} |
||||
|
||||
@Test |
||||
void onAuthenticationFailureWithAuthenticationServiceExceptionThenRethrows() { |
||||
AuthenticationEntryPointFailureHandlerAdapter failureHandler = new AuthenticationEntryPointFailureHandlerAdapter( |
||||
this.authenticationEntryPoint); |
||||
AuthenticationException failure = new AuthenticationServiceException("failed"); |
||||
assertThatExceptionOfType(AuthenticationServiceException.class) |
||||
.isThrownBy(() -> failureHandler.onAuthenticationFailure(this.request, this.response, failure)) |
||||
.isSameAs(failure); |
||||
verifyNoInteractions(this.authenticationEntryPoint); |
||||
} |
||||
|
||||
} |
||||
@ -0,0 +1,77 @@
@@ -0,0 +1,77 @@
|
||||
/* |
||||
* 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.server.authentication; |
||||
|
||||
import org.junit.jupiter.api.BeforeEach; |
||||
import org.junit.jupiter.api.Test; |
||||
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; |
||||
import org.springframework.web.server.ServerWebExchange; |
||||
import org.springframework.web.server.WebFilterChain; |
||||
|
||||
import static org.assertj.core.api.Assertions.assertThatExceptionOfType; |
||||
import static org.mockito.ArgumentMatchers.any; |
||||
import static org.mockito.BDDMockito.given; |
||||
import static org.mockito.Mockito.mock; |
||||
import static org.mockito.Mockito.verify; |
||||
import static org.mockito.Mockito.verifyNoInteractions; |
||||
|
||||
/** |
||||
* @author Daniel Garnier-Moiroux |
||||
* @since 5.8 |
||||
*/ |
||||
class ServerAuthenticationEntryPointFailureHandlerAdapterTest { |
||||
|
||||
private final ServerAuthenticationEntryPoint serverAuthenticationEntryPoint = mock( |
||||
ServerAuthenticationEntryPoint.class); |
||||
|
||||
private final ServerWebExchange serverWebExchange = mock(ServerWebExchange.class); |
||||
|
||||
private final WebFilterExchange webFilterExchange = new WebFilterExchange(this.serverWebExchange, |
||||
mock(WebFilterChain.class)); |
||||
|
||||
@BeforeEach |
||||
void setUp() { |
||||
given(this.serverAuthenticationEntryPoint.commence(any(), any())).willReturn(Mono.empty()); |
||||
} |
||||
|
||||
@Test |
||||
void onAuthenticationFailureThenCommenceAuthentication() { |
||||
ServerAuthenticationEntryPointFailureHandlerAdapter failureHandler = new ServerAuthenticationEntryPointFailureHandlerAdapter( |
||||
this.serverAuthenticationEntryPoint); |
||||
AuthenticationException failure = new AuthenticationException("failed") { |
||||
}; |
||||
failureHandler.onAuthenticationFailure(this.webFilterExchange, failure).block(); |
||||
verify(this.serverAuthenticationEntryPoint).commence(this.serverWebExchange, failure); |
||||
} |
||||
|
||||
@Test |
||||
void onAuthenticationFailureWithAuthenticationServiceExceptionThenRethrows() { |
||||
ServerAuthenticationEntryPointFailureHandlerAdapter failureHandler = new ServerAuthenticationEntryPointFailureHandlerAdapter( |
||||
this.serverAuthenticationEntryPoint); |
||||
AuthenticationException failure = new AuthenticationServiceException("failed"); |
||||
assertThatExceptionOfType(AuthenticationServiceException.class) |
||||
.isThrownBy(() -> failureHandler.onAuthenticationFailure(this.webFilterExchange, failure).block()) |
||||
.isSameAs(failure); |
||||
verifyNoInteractions(this.serverWebExchange); |
||||
} |
||||
|
||||
} |
||||
Loading…
Reference in new issue