Skip to content

Commit 200b7fe

Browse files
Kehrlannjzheaux
authored andcommitted
Add (Server)AuthenticationEntryPointFailureHandlerAdapter
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.
1 parent 56b9bad commit 200b7fe

File tree

9 files changed

+289
-9
lines changed

9 files changed

+289
-9
lines changed

etc/checkstyle/checkstyle-suppressions.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,4 +53,7 @@
5353
<suppress files="WithSecurityContextTestExecutionListenerTests\.java" checks="SpringMethodVisibility"/>
5454
<suppress files="AbstractOAuth2AuthorizationGrantRequestEntityConverter\.java" checks="SpringMethodVisibility"/>
5555
<suppress files="JoseHeader\.java" checks="SpringMethodVisibility"/>
56+
57+
<!-- Lambdas that we can't replace with a method reference because a closure is required -->
58+
<suppress files="BearerTokenAuthenticationFilter\.java" checks="SpringLambda"/>
5659
</suppressions>

oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java

Lines changed: 10 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,6 @@
2727
import org.springframework.security.authentication.AuthenticationDetailsSource;
2828
import org.springframework.security.authentication.AuthenticationManager;
2929
import org.springframework.security.authentication.AuthenticationManagerResolver;
30-
import org.springframework.security.authentication.AuthenticationServiceException;
3130
import org.springframework.security.core.Authentication;
3231
import org.springframework.security.core.AuthenticationException;
3332
import org.springframework.security.core.context.SecurityContext;
@@ -40,6 +39,7 @@
4039
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
4140
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
4241
import org.springframework.security.web.AuthenticationEntryPoint;
42+
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandlerAdapter;
4343
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
4444
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
4545
import org.springframework.security.web.context.NullSecurityContextRepository;
@@ -73,12 +73,12 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
7373

7474
private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
7575

76-
private AuthenticationFailureHandler authenticationFailureHandler = (request, response, exception) -> {
77-
if (exception instanceof AuthenticationServiceException) {
78-
throw exception;
79-
}
80-
this.authenticationEntryPoint.commence(request, response, exception);
81-
};
76+
private AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationEntryPointFailureHandlerAdapter(
77+
(request, response, authException) -> {
78+
// This is a lambda and not a method reference so that the FailureHandler
79+
// reflects entrypoint updates
80+
this.authenticationEntryPoint.commence(request, response, authException);
81+
});
8282

8383
private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
8484

@@ -192,7 +192,10 @@ public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
192192
* Set the {@link AuthenticationEntryPoint} to use. Defaults to
193193
* {@link BearerTokenAuthenticationEntryPoint}.
194194
* @param authenticationEntryPoint the {@code AuthenticationEntryPoint} to use
195+
* @deprecated use
196+
* {@link BearerTokenAuthenticationFilter#authenticationFailureHandler} instead
195197
*/
198+
@Deprecated
196199
public void setAuthenticationEntryPoint(final AuthenticationEntryPoint authenticationEntryPoint) {
197200
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
198201
this.authenticationEntryPoint = authenticationEntryPoint;

oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilterTests.java

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,12 +37,14 @@
3737
import org.springframework.security.authentication.AuthenticationManagerResolver;
3838
import org.springframework.security.authentication.AuthenticationServiceException;
3939
import org.springframework.security.authentication.TestingAuthenticationToken;
40+
import org.springframework.security.core.AuthenticationException;
4041
import org.springframework.security.core.context.SecurityContext;
4142
import org.springframework.security.core.context.SecurityContextHolderStrategy;
4243
import org.springframework.security.core.context.SecurityContextImpl;
4344
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
4445
import org.springframework.security.oauth2.server.resource.BearerTokenError;
4546
import org.springframework.security.oauth2.server.resource.BearerTokenErrorCodes;
47+
import org.springframework.security.oauth2.server.resource.InvalidBearerTokenException;
4648
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
4749
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
4850
import org.springframework.security.web.AuthenticationEntryPoint;
@@ -199,6 +201,19 @@ public void doFilterWhenAuthenticationServiceExceptionThenRethrows() {
199201
.isThrownBy(() -> filter.doFilter(this.request, this.response, this.filterChain));
200202
}
201203

204+
@Test
205+
public void doFilterWhenCustomEntryPointAndAuthenticationErrorThenUses() throws ServletException, IOException {
206+
AuthenticationException exception = new InvalidBearerTokenException("message");
207+
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
208+
given(this.authenticationManager.authenticate(any())).willThrow(exception);
209+
BearerTokenAuthenticationFilter filter = addMocks(
210+
new BearerTokenAuthenticationFilter(this.authenticationManager));
211+
AuthenticationEntryPoint entrypoint = mock(AuthenticationEntryPoint.class);
212+
filter.setAuthenticationEntryPoint(entrypoint);
213+
filter.doFilter(this.request, this.response, this.filterChain);
214+
verify(entrypoint).commence(any(), any(), any(InvalidBearerTokenException.class));
215+
}
216+
202217
@Test
203218
public void doFilterWhenCustomAuthenticationDetailsSourceThenUses() throws ServletException, IOException {
204219
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");

web/src/main/java/org/springframework/security/web/authentication/AuthenticationEntryPointFailureHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2019 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -31,7 +31,9 @@
3131
*
3232
* @author Sergey Bespalov
3333
* @since 5.2.0
34+
* @deprecated Use {@link AuthenticationEntryPointFailureHandlerAdapter} instead
3435
*/
36+
@Deprecated
3537
public class AuthenticationEntryPointFailureHandler implements AuthenticationFailureHandler {
3638

3739
private final AuthenticationEntryPoint authenticationEntryPoint;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.authentication;
18+
19+
import java.io.IOException;
20+
21+
import javax.servlet.ServletException;
22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletResponse;
24+
25+
import org.springframework.security.authentication.AuthenticationServiceException;
26+
import org.springframework.security.core.AuthenticationException;
27+
import org.springframework.security.web.AuthenticationEntryPoint;
28+
import org.springframework.util.Assert;
29+
30+
/**
31+
* Adapts a {@link AuthenticationEntryPoint} into a {@link AuthenticationFailureHandler}.
32+
* When the failure is an {@link AuthenticationServiceException}, it re-throws, to produce
33+
* an HTTP 500 error.
34+
*
35+
* @author Daniel Garnier-Moiroux
36+
* @since 5.8
37+
*/
38+
public final class AuthenticationEntryPointFailureHandlerAdapter implements AuthenticationFailureHandler {
39+
40+
private final AuthenticationEntryPoint authenticationEntryPoint;
41+
42+
public AuthenticationEntryPointFailureHandlerAdapter(AuthenticationEntryPoint authenticationEntryPoint) {
43+
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
44+
this.authenticationEntryPoint = authenticationEntryPoint;
45+
}
46+
47+
@Override
48+
public void onAuthenticationFailure(HttpServletRequest request, HttpServletResponse response,
49+
AuthenticationException failure) throws IOException, ServletException {
50+
if (AuthenticationServiceException.class.isAssignableFrom(failure.getClass())) {
51+
throw failure;
52+
}
53+
this.authenticationEntryPoint.commence(request, response, failure);
54+
}
55+
56+
}

web/src/main/java/org/springframework/security/web/server/authentication/ServerAuthenticationEntryPointFailureHandler.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2017 the original author or authors.
2+
* Copyright 2002-2022 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -29,7 +29,9 @@
2929
*
3030
* @author Rob Winch
3131
* @since 5.0
32+
* @deprecated use {@link ServerAuthenticationEntryPointFailureHandlerAdapter} instead.
3233
*/
34+
@Deprecated
3335
public class ServerAuthenticationEntryPointFailureHandler implements ServerAuthenticationFailureHandler {
3436

3537
private final ServerAuthenticationEntryPoint authenticationEntryPoint;
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,53 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.server.authentication;
18+
19+
import reactor.core.publisher.Mono;
20+
21+
import org.springframework.security.authentication.AuthenticationServiceException;
22+
import org.springframework.security.core.AuthenticationException;
23+
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
24+
import org.springframework.security.web.server.WebFilterExchange;
25+
import org.springframework.util.Assert;
26+
27+
/**
28+
* Adapts a {@link ServerAuthenticationEntryPoint} into a
29+
* {@link ServerAuthenticationFailureHandler}. When the failure is an
30+
* {@link AuthenticationServiceException}, it re-throws, to produce an HTTP 500 error.
31+
*
32+
* @author Daniel Garnier-Moiroux
33+
* @since 5.8
34+
*/
35+
public class ServerAuthenticationEntryPointFailureHandlerAdapter implements ServerAuthenticationFailureHandler {
36+
37+
private final ServerAuthenticationEntryPoint authenticationEntryPoint;
38+
39+
public ServerAuthenticationEntryPointFailureHandlerAdapter(
40+
ServerAuthenticationEntryPoint authenticationEntryPoint) {
41+
Assert.notNull(authenticationEntryPoint, "authenticationEntryPoint cannot be null");
42+
this.authenticationEntryPoint = authenticationEntryPoint;
43+
}
44+
45+
@Override
46+
public Mono<Void> onAuthenticationFailure(WebFilterExchange webFilterExchange, AuthenticationException exception) {
47+
if (AuthenticationServiceException.class.isAssignableFrom(exception.getClass())) {
48+
return Mono.error(exception);
49+
}
50+
return this.authenticationEntryPoint.commence(webFilterExchange.getExchange(), exception);
51+
}
52+
53+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.authentication;
18+
19+
import java.io.IOException;
20+
21+
import javax.servlet.ServletException;
22+
import javax.servlet.http.HttpServletRequest;
23+
import javax.servlet.http.HttpServletResponse;
24+
25+
import org.junit.jupiter.api.Test;
26+
27+
import org.springframework.security.authentication.AuthenticationServiceException;
28+
import org.springframework.security.core.AuthenticationException;
29+
import org.springframework.security.web.AuthenticationEntryPoint;
30+
31+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
32+
import static org.mockito.Mockito.mock;
33+
import static org.mockito.Mockito.verify;
34+
import static org.mockito.Mockito.verifyNoInteractions;
35+
36+
/**
37+
* @author Daniel Garnier-Moiroux
38+
* @since 5.8
39+
*/
40+
class AuthenticationEntryPointFailureHandlerAdapterTest {
41+
42+
private final AuthenticationEntryPoint authenticationEntryPoint = mock(AuthenticationEntryPoint.class);
43+
44+
private final HttpServletRequest request = mock(HttpServletRequest.class);
45+
46+
private final HttpServletResponse response = mock(HttpServletResponse.class);
47+
48+
@Test
49+
void onAuthenticationFailureThenCommenceAuthentication() throws ServletException, IOException {
50+
AuthenticationEntryPointFailureHandlerAdapter failureHandler = new AuthenticationEntryPointFailureHandlerAdapter(
51+
this.authenticationEntryPoint);
52+
AuthenticationException failure = new AuthenticationException("failed") {
53+
};
54+
failureHandler.onAuthenticationFailure(this.request, this.response, failure);
55+
verify(this.authenticationEntryPoint).commence(this.request, this.response, failure);
56+
}
57+
58+
@Test
59+
void onAuthenticationFailureWithAuthenticationServiceExceptionThenRethrows() {
60+
AuthenticationEntryPointFailureHandlerAdapter failureHandler = new AuthenticationEntryPointFailureHandlerAdapter(
61+
this.authenticationEntryPoint);
62+
AuthenticationException failure = new AuthenticationServiceException("failed");
63+
assertThatExceptionOfType(AuthenticationServiceException.class)
64+
.isThrownBy(() -> failureHandler.onAuthenticationFailure(this.request, this.response, failure))
65+
.isSameAs(failure);
66+
verifyNoInteractions(this.authenticationEntryPoint);
67+
}
68+
69+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
/*
2+
* Copyright 2002-2022 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.web.server.authentication;
18+
19+
import org.junit.jupiter.api.BeforeEach;
20+
import org.junit.jupiter.api.Test;
21+
import reactor.core.publisher.Mono;
22+
23+
import org.springframework.security.authentication.AuthenticationServiceException;
24+
import org.springframework.security.core.AuthenticationException;
25+
import org.springframework.security.web.server.ServerAuthenticationEntryPoint;
26+
import org.springframework.security.web.server.WebFilterExchange;
27+
import org.springframework.web.server.ServerWebExchange;
28+
import org.springframework.web.server.WebFilterChain;
29+
30+
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
31+
import static org.mockito.ArgumentMatchers.any;
32+
import static org.mockito.BDDMockito.given;
33+
import static org.mockito.Mockito.mock;
34+
import static org.mockito.Mockito.verify;
35+
import static org.mockito.Mockito.verifyNoInteractions;
36+
37+
/**
38+
* @author Daniel Garnier-Moiroux
39+
* @since 5.8
40+
*/
41+
class ServerAuthenticationEntryPointFailureHandlerAdapterTest {
42+
43+
private final ServerAuthenticationEntryPoint serverAuthenticationEntryPoint = mock(
44+
ServerAuthenticationEntryPoint.class);
45+
46+
private final ServerWebExchange serverWebExchange = mock(ServerWebExchange.class);
47+
48+
private final WebFilterExchange webFilterExchange = new WebFilterExchange(this.serverWebExchange,
49+
mock(WebFilterChain.class));
50+
51+
@BeforeEach
52+
void setUp() {
53+
given(this.serverAuthenticationEntryPoint.commence(any(), any())).willReturn(Mono.empty());
54+
}
55+
56+
@Test
57+
void onAuthenticationFailureThenCommenceAuthentication() {
58+
ServerAuthenticationEntryPointFailureHandlerAdapter failureHandler = new ServerAuthenticationEntryPointFailureHandlerAdapter(
59+
this.serverAuthenticationEntryPoint);
60+
AuthenticationException failure = new AuthenticationException("failed") {
61+
};
62+
failureHandler.onAuthenticationFailure(this.webFilterExchange, failure).block();
63+
verify(this.serverAuthenticationEntryPoint).commence(this.serverWebExchange, failure);
64+
}
65+
66+
@Test
67+
void onAuthenticationFailureWithAuthenticationServiceExceptionThenRethrows() {
68+
ServerAuthenticationEntryPointFailureHandlerAdapter failureHandler = new ServerAuthenticationEntryPointFailureHandlerAdapter(
69+
this.serverAuthenticationEntryPoint);
70+
AuthenticationException failure = new AuthenticationServiceException("failed");
71+
assertThatExceptionOfType(AuthenticationServiceException.class)
72+
.isThrownBy(() -> failureHandler.onAuthenticationFailure(this.webFilterExchange, failure).block())
73+
.isSameAs(failure);
74+
verifyNoInteractions(this.serverWebExchange);
75+
}
76+
77+
}

0 commit comments

Comments
 (0)