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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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 @@ |
|||||||
|
/* |
||||||
|
* 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