[a-zA-Z0-9-._~+/]+=*)$",
+ Pattern.CASE_INSENSITIVE);
+
+ private static final String ACCESS_TOKEN_PARAMETER_NAME = "access_token";
+
+ private boolean allowFormEncodedBodyParameter = false;
+
+ private boolean allowUriQueryParameter = false;
+
+ private String bearerTokenHeaderName = HttpHeaders.AUTHORIZATION;
+
+ @Override
+ public Authentication convert(HttpServletRequest request) {
+ String token = resolveToken(request);
+ if (StringUtils.hasText(token)) {
+ return new BearerTokenAuthenticationToken(token);
+ }
+ return null;
+ }
+
+ private String resolveToken(HttpServletRequest request) {
+ final String authorizationHeaderToken = resolveFromAuthorizationHeader(request);
+ final String parameterToken = isParameterTokenSupportedForRequest(request)
+ ? resolveFromRequestParameters(request) : null;
+ if (authorizationHeaderToken != null) {
+ if (parameterToken != null) {
+ final BearerTokenError error = BearerTokenErrors
+ .invalidRequest("Found multiple bearer tokens in the request");
+ throw new OAuth2AuthenticationException(error);
+ }
+ return authorizationHeaderToken;
+ }
+ if (parameterToken != null && isParameterTokenEnabledForRequest(request)) {
+ return parameterToken;
+ }
+ return null;
+ }
+
+ private String resolveFromAuthorizationHeader(HttpServletRequest request) {
+ String authorization = request.getHeader(this.bearerTokenHeaderName);
+ if (!StringUtils.startsWithIgnoreCase(authorization, "bearer")) {
+ return null;
+ }
+ Matcher matcher = authorizationPattern.matcher(authorization);
+ if (!matcher.matches()) {
+ BearerTokenError error = BearerTokenErrors.invalidToken("Bearer token is malformed");
+ throw new OAuth2AuthenticationException(error);
+ }
+ return matcher.group("token");
+ }
+
+ private boolean isParameterTokenEnabledForRequest(HttpServletRequest request) {
+ return ((this.allowFormEncodedBodyParameter && isFormEncodedRequest(request) && !isGetRequest(request)
+ && !hasAccessTokenInQueryString(request)) || (this.allowUriQueryParameter && isGetRequest(request)));
+ }
+
+ private static String resolveFromRequestParameters(HttpServletRequest request) {
+ String[] values = request.getParameterValues(ACCESS_TOKEN_PARAMETER_NAME);
+ if (values == null || values.length == 0) {
+ return null;
+ }
+ if (values.length == 1) {
+ return values[0];
+ }
+ BearerTokenError error = BearerTokenErrors.invalidRequest("Found multiple bearer tokens in the request");
+ throw new OAuth2AuthenticationException(error);
+ }
+
+ private boolean isParameterTokenSupportedForRequest(final HttpServletRequest request) {
+ return isFormEncodedRequest(request) || isGetRequest(request);
+ }
+
+ private boolean isGetRequest(HttpServletRequest request) {
+ return HttpMethod.GET.name().equals(request.getMethod());
+ }
+
+ private boolean isFormEncodedRequest(HttpServletRequest request) {
+ return MediaType.APPLICATION_FORM_URLENCODED_VALUE.equals(request.getContentType());
+ }
+
+ private static boolean hasAccessTokenInQueryString(HttpServletRequest request) {
+ return (request.getQueryString() != null) && request.getQueryString().contains(ACCESS_TOKEN_PARAMETER_NAME);
+ }
+
+ /**
+ * Set if transport of access token using URI query parameter is supported. Defaults
+ * to {@code false}.
+ *
+ * The spec recommends against using this mechanism for sending bearer tokens, and
+ * even goes as far as stating that it was only included for completeness.
+ * @param allowUriQueryParameter if the URI query parameter is supported
+ */
+ public void setAllowUriQueryParameter(boolean allowUriQueryParameter) {
+ this.allowUriQueryParameter = allowUriQueryParameter;
+ }
+
+ /**
+ * Set this value to configure what header is checked when resolving a Bearer Token.
+ * This value is defaulted to {@link HttpHeaders#AUTHORIZATION}.
+ *
+ * This allows other headers to be used as the Bearer Token source such as
+ * {@link HttpHeaders#PROXY_AUTHORIZATION}
+ * @param bearerTokenHeaderName the header to check when retrieving the Bearer Token.
+ */
+ public void setBearerTokenHeaderName(String bearerTokenHeaderName) {
+ this.bearerTokenHeaderName = bearerTokenHeaderName;
+ }
+
+ /**
+ * Set if transport of access token using form-encoded body parameter is supported.
+ * Defaults to {@code false}.
+ * @param allowFormEncodedBodyParameter if the form-encoded body parameter is
+ * supported
+ */
+ public void setAllowFormEncodedBodyParameter(boolean allowFormEncodedBodyParameter) {
+ this.allowFormEncodedBodyParameter = allowFormEncodedBodyParameter;
+ }
+
+}
diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java
index 5ffd2ed8884..0c56b576a5e 100644
--- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java
+++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationFilter.java
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2021 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -24,6 +24,7 @@
import jakarta.servlet.http.HttpServletResponse;
import org.springframework.core.log.LogMessage;
+import org.springframework.security.authentication.AbstractAuthenticationToken;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.authentication.AuthenticationManager;
import org.springframework.security.authentication.AuthenticationManagerResolver;
@@ -39,19 +40,21 @@
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* Authenticates requests that contain an OAuth 2.0
* Bearer
* Token.
- *
+ *
* This filter should be wired with an {@link AuthenticationManager} that can authenticate
* a {@link BearerTokenAuthenticationToken}.
*
@@ -76,12 +79,12 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
private AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationEntryPointFailureHandler(
(request, response, exception) -> this.authenticationEntryPoint.commence(request, response, exception));
- private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
-
private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource();
private SecurityContextRepository securityContextRepository = new RequestAttributeSecurityContextRepository();
+ private AuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
+
/**
* Construct a {@code BearerTokenAuthenticationFilter} using the provided parameter(s)
* @param authenticationManagerResolver
@@ -114,23 +117,25 @@ public BearerTokenAuthenticationFilter(AuthenticationManager authenticationManag
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
- String token;
+ Authentication authenticationRequest;
try {
- token = this.bearerTokenResolver.resolve(request);
+ authenticationRequest = this.authenticationConverter.convert(request);
}
catch (OAuth2AuthenticationException invalid) {
this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid);
this.authenticationEntryPoint.commence(request, response, invalid);
return;
}
- if (token == null) {
+
+ if (authenticationRequest == null) {
this.logger.trace("Did not process request since did not find bearer token");
filterChain.doFilter(request, response);
return;
}
- BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
- authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
+ if (authenticationRequest instanceof AbstractAuthenticationToken details) {
+ details.setDetails(this.authenticationDetailsSource.buildDetails(request));
+ }
try {
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
@@ -181,7 +186,14 @@ public void setSecurityContextRepository(SecurityContextRepository securityConte
*/
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
- this.bearerTokenResolver = bearerTokenResolver;
+ this.authenticationConverter = (request) -> {
+ String token = bearerTokenResolver.resolve(request);
+ if (!StringUtils.hasText(token)) {
+ this.logger.trace("Did not process request since did not find bearer token");
+ return null;
+ }
+ return new BearerTokenAuthenticationToken(token);
+ };
}
/**
@@ -208,7 +220,7 @@ public void setAuthenticationFailureHandler(final AuthenticationFailureHandler a
/**
* Set the {@link AuthenticationDetailsSource} to use. Defaults to
* {@link WebAuthenticationDetailsSource}.
- * @param authenticationDetailsSource the {@code AuthenticationConverter} to use
+ * @param authenticationDetailsSource the {@code AuthenticationDetailsSource} to use
* @since 5.5
*/
public void setAuthenticationDetailsSource(
@@ -217,4 +229,15 @@ public void setAuthenticationDetailsSource(
this.authenticationDetailsSource = authenticationDetailsSource;
}
+ /**
+ * Set the {@link AuthenticationConverter} to use. Defaults to
+ * {@link BearerTokenAuthenticationConverter}.
+ * @param authenticationConverter the {@code AuthenticationConverter} to use
+ * @since 6.3
+ */
+ public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
+ Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
+ this.authenticationConverter = authenticationConverter;
+ }
+
}
diff --git a/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java
new file mode 100644
index 00000000000..80358b7b7d6
--- /dev/null
+++ b/oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverterTests.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2002-2024 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.oauth2.server.resource.web.authentication;
+
+import org.junit.jupiter.api.Test;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.http.HttpMethod;
+import org.springframework.http.MediaType;
+import org.springframework.mock.web.MockHttpServletRequest;
+import org.springframework.security.core.Authentication;
+import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
+
+/**
+ * Tests for {@link BearerTokenAuthenticationConverter}
+ *
+ * @author Max Batischev
+ */
+public class BearerTokenAuthenticationConverterTests {
+
+ private static final String X_AUTH_TOKEN_HEADER = "X-Auth-Token";
+
+ private static final String TEST_X_AUTH_TOKEN = "test-x-auth-token";
+
+ private static final String BEARER_TOKEN = "test_bearer_token";
+
+ private final BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
+
+ @Test
+ public void convertWhenAuthorizationHeaderIsPresentThenTokenIsConverted() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN);
+
+ Authentication authentication = this.converter.convert(request);
+
+ assertThat(authentication).isNotNull();
+ }
+
+ @Test
+ public void convertWhenQueryParameterIsPresentThenTokenIsConverted() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod(HttpMethod.GET.name());
+ request.addParameter("access_token", BEARER_TOKEN);
+
+ this.converter.setAllowUriQueryParameter(true);
+
+ Authentication authentication = this.converter.convert(request);
+ assertThat(authentication).isNotNull();
+ }
+
+ @Test
+ public void convertWhenAuthorizationHeaderNotIsPresentThenTokenIsNotConverted() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+
+ Authentication authentication = this.converter.convert(request);
+
+ assertThat(authentication).isNull();
+ }
+
+ @Test
+ public void convertWhenAuthorizationHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addParameter("access_token", BEARER_TOKEN);
+ request.setMethod(HttpMethod.GET.name());
+ request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN);
+
+ assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request))
+ .withMessageContaining("Found multiple bearer tokens in the request");
+ }
+
+ @Test
+ public void convertWhenXAuthTokenHeaderIsPresentAndBearerTokenHeaderNameSetThenTokenIsConverted() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader(X_AUTH_TOKEN_HEADER, "Bearer " + TEST_X_AUTH_TOKEN);
+
+ this.converter.setBearerTokenHeaderName(X_AUTH_TOKEN_HEADER);
+
+ Authentication authentication = this.converter.convert(request);
+ assertThat(authentication).isNotNull();
+ }
+
+ @Test
+ public void convertWhenHeaderWithMissingTokenIsPresentThenAuthenticationExceptionIsThrown() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer ");
+
+ assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request))
+ .withMessageContaining(("Bearer token is malformed"));
+ }
+
+ @Test
+ public void convertWhenHeaderWithInvalidCharactersIsPresentThenAuthenticationExceptionIsThrown() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer an\"invalid\"token");
+
+ assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request))
+ .withMessageContaining(("Bearer token is malformed"));
+ }
+
+ @Test
+ public void convertWhenFormParameterIsPresentAndAllowFormEncodedBodyParameterThenConverted() {
+ MockHttpServletRequest request = new MockHttpServletRequest();
+ request.setMethod(HttpMethod.POST.name());
+ request.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
+ request.addParameter("access_token", BEARER_TOKEN);
+ this.converter.setAllowFormEncodedBodyParameter(true);
+
+ assertThat(this.converter.convert(request)).isNotNull();
+ }
+
+}
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 63d306f13e8..c9547016246 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
@@ -1,5 +1,5 @@
/*
- * Copyright 2002-2022 the original author or authors.
+ * Copyright 2002-2024 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.
@@ -47,6 +47,7 @@
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.web.AuthenticationEntryPoint;
+import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
@@ -69,6 +70,8 @@
@ExtendWith(MockitoExtension.class)
public class BearerTokenAuthenticationFilterTests {
+ private static final String TEST_TOKEN = "token";
+
@Mock
AuthenticationEntryPoint authenticationEntryPoint;
@@ -78,6 +81,9 @@ public class BearerTokenAuthenticationFilterTests {
@Mock
AuthenticationManager authenticationManager;
+ @Mock
+ AuthenticationConverter authenticationConverter;
+
@Mock
AuthenticationManagerResolver authenticationManagerResolver;
@@ -102,7 +108,7 @@ public void httpMocks() {
@Test
public void doFilterWhenBearerTokenPresentThenAuthenticates() throws ServletException, IOException {
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.doFilter(this.request, this.response, this.filterChain);
@@ -117,12 +123,13 @@ public void doFilterWhenBearerTokenPresentThenAuthenticates() throws ServletExce
@Test
public void doFilterWhenSecurityContextRepositoryThenSaves() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
- String token = "token";
+ String token = TEST_TOKEN;
given(this.bearerTokenResolver.resolve(this.request)).willReturn(token);
TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("test", "password");
given(this.authenticationManager.authenticate(any())).willReturn(expectedAuthentication);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
+
filter.setSecurityContextRepository(securityContextRepository);
filter.doFilter(this.request, this.response, this.filterChain);
ArgumentCaptor captor = ArgumentCaptor
@@ -138,13 +145,13 @@ public void doFilterWhenSecurityContextRepositoryThenSaves() throws ServletExcep
public void doFilterWhenUsingAuthenticationManagerResolverThenAuthenticates() throws Exception {
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManagerResolver));
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManagerResolver.resolve(any())).willReturn(this.authenticationManager);
filter.doFilter(this.request, this.response, this.filterChain);
ArgumentCaptor captor = ArgumentCaptor
.forClass(BearerTokenAuthenticationToken.class);
verify(this.authenticationManager).authenticate(captor.capture());
- assertThat(captor.getValue().getPrincipal()).isEqualTo("token");
+ assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN);
assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME))
.isNotNull();
}
@@ -171,7 +178,7 @@ public void doFilterWhenAuthenticationFailsWithDefaultHandlerThenPropagatesError
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED,
"description", "uri");
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error);
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@@ -185,7 +192,7 @@ public void doFilterWhenAuthenticationFailsWithCustomHandlerThenPropagatesError(
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED,
"description", "uri");
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error);
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@@ -197,7 +204,7 @@ public void doFilterWhenAuthenticationFailsWithCustomHandlerThenPropagatesError(
@Test
public void doFilterWhenAuthenticationServiceExceptionThenRethrows() {
AuthenticationServiceException exception = new AuthenticationServiceException("message");
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any())).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@@ -208,7 +215,7 @@ public void doFilterWhenAuthenticationServiceExceptionThenRethrows() {
@Test
public void doFilterWhenCustomEntryPointAndAuthenticationErrorThenUses() throws ServletException, IOException {
AuthenticationException exception = new InvalidBearerTokenException("message");
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any())).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@@ -220,7 +227,7 @@ public void doFilterWhenCustomEntryPointAndAuthenticationErrorThenUses() throws
@Test
public void doFilterWhenCustomAuthenticationDetailsSourceThenUses() throws ServletException, IOException {
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.doFilter(this.request, this.response, this.filterChain);
@@ -229,7 +236,7 @@ public void doFilterWhenCustomAuthenticationDetailsSourceThenUses() throws Servl
@Test
public void doFilterWhenCustomSecurityContextHolderStrategyThenUses() throws ServletException, IOException {
- given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
+ given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
@@ -260,7 +267,7 @@ public void setBearerTokenResolverWhenNullThenThrowsException() {
}
@Test
- public void setAuthenticationConverterWhenNullThenThrowsException() {
+ public void setAuthenticationDetailsSourceWhenNullThenThrowsException() {
// @formatter:off
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager);
assertThatIllegalArgumentException()
@@ -269,6 +276,16 @@ public void setAuthenticationConverterWhenNullThenThrowsException() {
// @formatter:on
}
+ @Test
+ public void setAuthenticationConverterWhenNullThenThrowsException() {
+ // @formatter:off
+ BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager);
+ assertThatIllegalArgumentException()
+ .isThrownBy(() -> filter.setAuthenticationConverter(null))
+ .withMessageContaining("authenticationConverter cannot be null");
+ // @formatter:on
+ }
+
@Test
public void constructorWhenNullAuthenticationManagerThenThrowsException() {
// @formatter:off
@@ -287,6 +304,171 @@ public void constructorWhenNullAuthenticationManagerResolverThenThrowsException(
// @formatter:on
}
+ @Test
+ public void doFilterWhenBearerTokenPresentAndConverterSetThenAuthenticates() throws ServletException, IOException {
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ ArgumentCaptor captor = ArgumentCaptor
+ .forClass(BearerTokenAuthenticationToken.class);
+ verify(this.authenticationManager).authenticate(captor.capture());
+ assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN);
+ assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME))
+ .isNotNull();
+ }
+
+ @Test
+ public void doFilterWhenSecurityContextRepositoryAndConverterSetThenSaves() throws ServletException, IOException {
+ SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("test", "password");
+ given(this.authenticationManager.authenticate(any())).willReturn(expectedAuthentication);
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+ filter.setSecurityContextRepository(securityContextRepository);
+
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ ArgumentCaptor captor = ArgumentCaptor
+ .forClass(BearerTokenAuthenticationToken.class);
+ verify(this.authenticationManager).authenticate(captor.capture());
+ assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN);
+ ArgumentCaptor contextArg = ArgumentCaptor.forClass(SecurityContext.class);
+ verify(securityContextRepository).saveContext(contextArg.capture(), eq(this.request), eq(this.response));
+ assertThat(contextArg.getValue().getAuthentication().getName()).isEqualTo(expectedAuthentication.getName());
+ }
+
+ @Test
+ public void doFilterWhenUsingAuthenticationManagerResolverAndConverterSetThenAuthenticates() throws Exception {
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManagerResolver));
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ given(this.authenticationManagerResolver.resolve(any())).willReturn(this.authenticationManager);
+
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ ArgumentCaptor captor = ArgumentCaptor
+ .forClass(BearerTokenAuthenticationToken.class);
+ verify(this.authenticationManager).authenticate(captor.capture());
+ assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN);
+ assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME))
+ .isNotNull();
+ }
+
+ @Test
+ public void doFilterWhenNoBearerTokenPresentAndConverterSetThenDoesNotAuthenticate()
+ throws ServletException, IOException {
+ given(this.authenticationConverter.convert(this.request)).willReturn(null);
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ verifyNoMoreInteractions(this.authenticationManager);
+ }
+
+ @Test
+ public void doFilterWhenMalformedBearerTokenAndConverterSetThenPropagatesError()
+ throws ServletException, IOException {
+ BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_REQUEST, HttpStatus.BAD_REQUEST,
+ "description", "uri");
+ OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error);
+ given(this.authenticationConverter.convert(this.request)).willThrow(exception);
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ verifyNoMoreInteractions(this.authenticationManager);
+ verify(this.authenticationEntryPoint).commence(this.request, this.response, exception);
+ }
+
+ @Test
+ public void doFilterWhenAuthenticationFailsWithDefaultHandlerAndConverterSetThenPropagatesError()
+ throws ServletException, IOException {
+ BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED,
+ "description", "uri");
+ OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error);
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception);
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ verify(this.authenticationEntryPoint).commence(this.request, this.response, exception);
+ }
+
+ @Test
+ public void doFilterWhenAuthenticationFailsWithCustomHandlerAndConverterSetThenPropagatesError()
+ throws ServletException, IOException {
+ BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED,
+ "description", "uri");
+ OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error);
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception);
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+ filter.setAuthenticationFailureHandler(this.authenticationFailureHandler);
+
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ verify(this.authenticationFailureHandler).onAuthenticationFailure(this.request, this.response, exception);
+ }
+
+ @Test
+ public void doFilterWhenConverterSetAndAuthenticationServiceExceptionThenRethrows() {
+ AuthenticationServiceException exception = new AuthenticationServiceException("message");
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ given(this.authenticationManager.authenticate(any())).willThrow(exception);
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+
+ assertThatExceptionOfType(AuthenticationServiceException.class)
+ .isThrownBy(() -> filter.doFilter(this.request, this.response, this.filterChain));
+ }
+
+ @Test
+ public void doFilterWhenConverterSetAndCustomEntryPointAndAuthenticationErrorThenUses()
+ throws ServletException, IOException {
+ AuthenticationException exception = new InvalidBearerTokenException("message");
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ given(this.authenticationManager.authenticate(any())).willThrow(exception);
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ 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 doFilterWhenConverterSetCustomSecurityContextHolderStrategyThenUses()
+ throws ServletException, IOException {
+ given(this.authenticationConverter.convert(this.request))
+ .willReturn(new BearerTokenAuthenticationToken(TEST_TOKEN));
+ BearerTokenAuthenticationFilter filter = addMocksWithConverter(
+ new BearerTokenAuthenticationFilter(this.authenticationManager));
+ SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
+ given(strategy.createEmptyContext()).willReturn(new SecurityContextImpl());
+ filter.setSecurityContextHolderStrategy(strategy);
+
+ filter.doFilter(this.request, this.response, this.filterChain);
+
+ verify(strategy).setContext(any());
+ }
+
private BearerTokenAuthenticationFilter addMocks(BearerTokenAuthenticationFilter filter) {
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter.setBearerTokenResolver(this.bearerTokenResolver);
@@ -294,6 +476,12 @@ private BearerTokenAuthenticationFilter addMocks(BearerTokenAuthenticationFilter
return filter;
}
+ private BearerTokenAuthenticationFilter addMocksWithConverter(BearerTokenAuthenticationFilter filter) {
+ filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
+ filter.setAuthenticationConverter(this.authenticationConverter);
+ return filter;
+ }
+
private void dontAuthenticate() throws ServletException, IOException {
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));