diff --git a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java index 8d44dfdcb5..c4b976bc99 100644 --- a/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java +++ b/config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java @@ -23,8 +23,6 @@ import java.util.Map; import java.util.function.Supplier; import jakarta.servlet.http.HttpServletRequest; -import org.apache.commons.logging.Log; -import org.apache.commons.logging.LogFactory; import org.springframework.context.ApplicationContext; import org.springframework.core.convert.converter.Converter; @@ -44,7 +42,6 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; import org.springframework.security.oauth2.jwt.Jwt; import org.springframework.security.oauth2.jwt.JwtDecoder; import org.springframework.security.oauth2.jwt.NimbusJwtDecoder; -import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; @@ -69,7 +66,6 @@ import org.springframework.security.web.util.matcher.OrRequestMatcher; import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher; import org.springframework.security.web.util.matcher.RequestMatcher; import org.springframework.util.Assert; -import org.springframework.util.StringUtils; import org.springframework.web.accept.ContentNegotiationStrategy; import org.springframework.web.accept.HeaderContentNegotiationStrategy; @@ -200,13 +196,9 @@ public final class OAuth2ResourceServerConfigurer bearerTokenResolver(BearerTokenResolver bearerTokenResolver) { Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); + this.authenticationConverter = new BearerTokenResolverHoldingAuthenticationConverter(bearerTokenResolver); return this; } @@ -214,7 +206,7 @@ public final class OAuth2ResourceServerConfigurer authenticationConverter(AuthenticationConverter authenticationConverter) { Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); @@ -299,10 +291,9 @@ public final class OAuth2ResourceServerConfigurer authenticationManager; } - BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver); AuthenticationConverter converter = getAuthenticationConverter(); this.requestMatcher.setAuthenticationConverter(converter); - filter.setAuthenticationConverter(converter); + BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver, converter); filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy()); filter = postProcess(filter); @@ -394,7 +385,7 @@ public final class OAuth2ResourceServerConfigurer 0) { BearerTokenResolver bearerTokenResolver = this.context.getBean(BearerTokenResolver.class); - this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver); + this.authenticationConverter = new BearerTokenResolverHoldingAuthenticationConverter(bearerTokenResolver); } else { this.authenticationConverter = new BearerTokenAuthenticationConverter(); @@ -404,7 +395,7 @@ public final class OAuth2ResourceServerConfigurer - - - Reference to a AuthenticationConverter - - - diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc index 15d15b191b..bbf8622dfe 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.rnc @@ -650,6 +650,9 @@ oauth2-resource-server.attlist &= oauth2-resource-server.attlist &= ## Reference to a AuthenticationEntryPoint attribute entry-point-ref {xsd:token}? +oauth2-resource-server.attlist &= + ## Reference to a AuthenticationConverter + attribute authentication-converter-ref {xsd:token}? jwt = ## Configures JWT authentication diff --git a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd index 34556b5549..2e3d6cf275 100644 --- a/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd +++ b/config/src/main/resources/org/springframework/security/config/spring-security-7.0.xsd @@ -1999,6 +1999,12 @@ + + + Reference to a AuthenticationConverter + + + diff --git a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java index 5c655f5afd..23dde67586 100644 --- a/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java +++ b/config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java @@ -2571,16 +2571,20 @@ public class OAuth2ResourceServerConfigurerTests { @Bean AuthenticationConverter authenticationConverterOne() { - BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); - converter.setAllowUriQueryParameter(true); - return converter; + DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); + resolver.setAllowUriQueryParameter(true); + BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); + authenticationConverter.setBearerTokenResolver(resolver); + return authenticationConverter; } @Bean AuthenticationConverter authenticationConverterTwo() { - BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); - converter.setAllowUriQueryParameter(true); - return converter; + DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); + resolver.setAllowUriQueryParameter(true); + BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter(); + authenticationConverter.setBearerTokenResolver(resolver); + return authenticationConverter; } } diff --git a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc index 6cdb9043dd..293abadddb 100644 --- a/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc +++ b/docs/modules/ROOT/pages/migration/servlet/oauth2.adoc @@ -115,3 +115,58 @@ fun authenticationConverter(val registrations: RelyingPartyRegistrationRepositor ====== If you must continue using `Saml2AuthenticationTokenConverter`, `OpenSaml4AuthenticationTokenConverter`, or `OpenSaml5AuthenticationTokenConverter` to process GET requests, you can call `setShouldConvertGetRequests` to `true.` + +== Provide an AuthenticationConverter to BearerTokenAuthenticationFilter + +In Spring Security 7, `BearerTokenAuthenticationFilter#setBearerTokenResolver` and `#setAuthenticaionDetailsSource` are deprecated in favor of configuring those on `BearerTokenAuthenticationConverter`. + +The `oauth2ResourceServer` DSL addresses most use cases and you need to nothing. + +If you are setting a `BearerTokenResolver` or `AuthenticationDetailsSource` directly on `BearerTokenAuthenticationFilter` similar to the following: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager); +filter.setBearerTokenResolver(myBearerTokenResolver); +filter.setAuthenticationDetailsSource(myAuthenticationDetailsSource); +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +val filter = BearerTokenAuthenticationFilter(authenticationManager) +filter.setBearerTokenResolver(myBearerTokenResolver) +filter.setAuthenticationDetailsSource(myAuthenticationDetailsSource) +---- +====== + +you are encouraged to use `BearerTokenAuthenticationConverter` to specify both: + +[tabs] +====== +Java:: ++ +[source,java,role="primary"] +---- +BearerTokenAuthenticationConverter authenticationConverter = + new BearerTokenAuthenticationConverter(); +authenticationConverter.setBearerTokenResolver(myBearerTokenResolver); +authenticationConverter.setAuthenticationDetailsSource(myAuthenticationDetailsSource); +BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(authenticationManager, authenicationConverter); +---- + +Kotlin:: ++ +[source,kotlin,role="secondary"] +---- +val authenticationConverter = BearerTokenAuthenticationConverter() +authenticationConverter.setBearerTokenResolver(myBearerTokenResolver) +authenticationConverter.setAuthenticationDetailsSource(myAuthenticationDetailsSource) +val filter = BearerTokenAuthenticationFilter(authenticationManager, authenticationConverter) +---- +====== diff --git a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc index d5438e0f43..8979d5ad29 100644 --- a/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc +++ b/docs/modules/ROOT/pages/servlet/appendix/namespace/http.adoc @@ -1266,7 +1266,8 @@ Reference to an `AuthenticationManagerResolver` which will resolve the `Authenti [[nsa-oauth2-resource-server-bearer-token-resolver-ref]] * **bearer-token-resolver-ref** -Reference to a `BearerTokenResolver` which will retrieve the bearer token from the request +Reference to a `BearerTokenResolver` which will retrieve the bearer token from the request. +This cannot be used in conjunction with `authentication-converter-ref` [[nsa-oauth2-resource-server-entry-point-ref]] * **entry-point-ref** @@ -1274,7 +1275,8 @@ Reference to a `AuthenticationEntryPoint` which will handle unauthorized request [[nsa-oauth2-resource-server-authentication-converter-ref]] * **authentication-converter-ref** -Reference to a `AuthenticationConverter` which convert request to authentication +Reference to a `AuthenticationConverter` which convert request to authentication. +This cannot be used in conjunction with `bearer-token-resolver-ref` [[nsa-jwt]] == diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java index 0fd023c5f6..7abd174630 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenResolver.java @@ -1,5 +1,5 @@ /* - * Copyright 2002-2025 the original author or authors. + * Copyright 2002-2020 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. @@ -29,10 +29,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException; * @since 5.1 * @see RFC 6750 * Section 2: Authenticated Requests - * @deprecated Use - * {@link org.springframework.security.web.authentication.AuthenticationConverter} instead */ -@Deprecated @FunctionalInterface public interface BearerTokenResolver { diff --git a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java index 211a49bf21..9f7e91a40a 100644 --- a/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java +++ b/oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/authentication/BearerTokenAuthenticationConverter.java @@ -16,20 +16,13 @@ package org.springframework.security.oauth2.server.resource.web.authentication; -import java.util.regex.Matcher; -import java.util.regex.Pattern; - import jakarta.servlet.http.HttpServletRequest; -import org.springframework.http.HttpHeaders; -import org.springframework.http.HttpMethod; -import org.springframework.http.MediaType; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.core.Authentication; -import org.springframework.security.oauth2.core.OAuth2AuthenticationException; -import org.springframework.security.oauth2.server.resource.BearerTokenError; -import org.springframework.security.oauth2.server.resource.BearerTokenErrors; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; +import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; +import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import org.springframework.security.web.authentication.AuthenticationConverter; import org.springframework.security.web.authentication.WebAuthenticationDetailsSource; import org.springframework.util.Assert; @@ -40,131 +33,29 @@ import org.springframework.util.StringUtils; * {@link BearerTokenAuthenticationToken} * * @author Max Batischev - * @since 6.5 + * @author Josh Cummings + * @since 7.0 */ public final class BearerTokenAuthenticationConverter implements AuthenticationConverter { private AuthenticationDetailsSource authenticationDetailsSource = new WebAuthenticationDetailsSource(); - private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?[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; + private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver(); @Override public Authentication convert(HttpServletRequest request) { - String token = resolveToken(request); + String token = this.bearerTokenResolver.resolve(request); if (StringUtils.hasText(token)) { BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(token); authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request)); - return authenticationToken; } 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; + public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) { + Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); + this.bearerTokenResolver = bearerTokenResolver; } /** 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 5aa819f6be..6a5f5c4869 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 @@ -40,6 +40,7 @@ import org.springframework.security.oauth2.server.resource.BearerTokenErrors; import org.springframework.security.oauth2.server.resource.authentication.AbstractOAuth2TokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken; import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider; +import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider; import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint; import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver; import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; @@ -76,6 +77,8 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { private final AuthenticationManagerResolver authenticationManagerResolver; + private final AuthenticationConverter authenticationConverter; + private SecurityContextHolderStrategy securityContextHolderStrategy = SecurityContextHolder .getContextHolderStrategy(); @@ -84,20 +87,15 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { private AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationEntryPointFailureHandler( (request, response, exception) -> this.authenticationEntryPoint.commence(request, response, exception)); - 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 */ public BearerTokenAuthenticationFilter( AuthenticationManagerResolver authenticationManagerResolver) { - Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null"); - this.authenticationManagerResolver = authenticationManagerResolver; + this(authenticationManagerResolver, new BearerTokenAuthenticationConverter()); } /** @@ -105,8 +103,43 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { * @param authenticationManager */ public BearerTokenAuthenticationFilter(AuthenticationManager authenticationManager) { + this(authenticationManager, new BearerTokenAuthenticationConverter()); + } + + /** + * Construct this filter using the provided parameters + * @param authenticationManager the {@link AuthenticationManager} to use + * @param authenticationConverter the {@link AuthenticationConverter} to use + * @since 7.0 + * @see JwtAuthenticationProvider + * @see OpaqueTokenAuthenticationProvider + * @see BearerTokenAuthenticationConverter + */ + public BearerTokenAuthenticationFilter(AuthenticationManager authenticationManager, + AuthenticationConverter authenticationConverter) { Assert.notNull(authenticationManager, "authenticationManager cannot be null"); - this.authenticationManagerResolver = (request) -> authenticationManager; + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); + this.authenticationManagerResolver = (authentication) -> authenticationManager; + this.authenticationConverter = authenticationConverter; + } + + /** + * Construct this filter using the provided parameters + * @param authenticationManagerResolver the {@link AuthenticationManagerResolver} to + * use + * @param authenticationConverter the {@link AuthenticationConverter} to use + * @since 7.0 + * @see JwtAuthenticationProvider + * @see OpaqueTokenAuthenticationProvider + * @see BearerTokenAuthenticationConverter + */ + public BearerTokenAuthenticationFilter( + AuthenticationManagerResolver authenticationManagerResolver, + AuthenticationConverter authenticationConverter) { + Assert.notNull(authenticationManagerResolver, "authenticationManagerResolver cannot be null"); + Assert.notNull(authenticationConverter, "authenticationConverter cannot be null"); + this.authenticationManagerResolver = authenticationManagerResolver; + this.authenticationConverter = authenticationConverter; } /** @@ -190,17 +223,20 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { * Set the {@link BearerTokenResolver} to use. Defaults to * {@link DefaultBearerTokenResolver}. * @param bearerTokenResolver the {@code BearerTokenResolver} to use + * @deprecated Please provide an {@link AuthenticationConverter} in the constructor + * instead + * @see BearerTokenAuthenticationConverter */ + @Deprecated public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) { Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null"); - 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); - }; + if (this.authenticationConverter instanceof BearerTokenAuthenticationConverter converter) { + converter.setBearerTokenResolver(bearerTokenResolver); + } + else { + throw new IllegalArgumentException( + "You cannot both specify an AuthenticationConverter and a BearerTokenResolver."); + } } /** @@ -227,13 +263,24 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { /** * 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 + * @deprecated Please provide an {@link AuthenticationConverter} in the constructor + * and set the {@link AuthenticationDetailsSource} there instead. For example, you can + * use {@link BearerTokenAuthenticationConverter#setAuthenticationDetailsSource} + * @see BearerTokenAuthenticationConverter */ + @Deprecated public void setAuthenticationDetailsSource( AuthenticationDetailsSource authenticationDetailsSource) { Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null"); - this.authenticationDetailsSource = authenticationDetailsSource; + if (this.authenticationConverter instanceof BearerTokenAuthenticationConverter converter) { + converter.setAuthenticationDetailsSource(authenticationDetailsSource); + } + else { + throw new IllegalArgumentException( + "You cannot specify both an AuthenticationConverter and an AuthenticationDetailsSource"); + } } private static boolean isDPoPBoundAccessToken(Authentication authentication) { @@ -249,15 +296,4 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter { return StringUtils.hasText(jwkThumbprintClaim); } - /** - * Set the {@link AuthenticationConverter} to use. Defaults to - * {@link BearerTokenAuthenticationConverter}. - * @param authenticationConverter the {@code AuthenticationConverter} to use - * @since 6.5 - */ - 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 index a5655a0c11..061f3b2232 100644 --- 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 @@ -27,6 +27,7 @@ import org.springframework.mock.web.MockHttpServletRequest; import org.springframework.security.authentication.AuthenticationDetailsSource; import org.springframework.security.core.Authentication; import org.springframework.security.oauth2.core.OAuth2AuthenticationException; +import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver; import static org.assertj.core.api.Assertions.assertThat; import static org.assertj.core.api.Assertions.assertThatExceptionOfType; @@ -46,8 +47,14 @@ public class BearerTokenAuthenticationConverterTests { private static final String BEARER_TOKEN = "test_bearer_token"; + private final DefaultBearerTokenResolver resolver = new DefaultBearerTokenResolver(); + private final BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter(); + { + this.converter.setBearerTokenResolver(this.resolver); + } + @Test public void convertWhenAuthorizationHeaderIsPresentThenTokenIsConverted() { MockHttpServletRequest request = new MockHttpServletRequest(); @@ -64,7 +71,7 @@ public class BearerTokenAuthenticationConverterTests { request.setMethod(HttpMethod.GET.name()); request.addParameter("access_token", BEARER_TOKEN); - this.converter.setAllowUriQueryParameter(true); + this.resolver.setAllowUriQueryParameter(true); Authentication authentication = this.converter.convert(request); assertThat(authentication).isNotNull(); @@ -86,6 +93,7 @@ public class BearerTokenAuthenticationConverterTests { request.setMethod(HttpMethod.GET.name()); request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN); + this.resolver.setAllowUriQueryParameter(true); assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request)) .withMessageContaining("Found multiple bearer tokens in the request"); } @@ -95,7 +103,7 @@ public class BearerTokenAuthenticationConverterTests { MockHttpServletRequest request = new MockHttpServletRequest(); request.addHeader(X_AUTH_TOKEN_HEADER, "Bearer " + TEST_X_AUTH_TOKEN); - this.converter.setBearerTokenHeaderName(X_AUTH_TOKEN_HEADER); + this.resolver.setBearerTokenHeaderName(X_AUTH_TOKEN_HEADER); Authentication authentication = this.converter.convert(request); assertThat(authentication).isNotNull(); @@ -140,7 +148,7 @@ public class BearerTokenAuthenticationConverterTests { request.setMethod(HttpMethod.POST.name()); request.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE); request.addParameter("access_token", BEARER_TOKEN); - this.converter.setAllowFormEncodedBodyParameter(true); + this.resolver.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 b64a29f762..67131e21b6 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 @@ -75,8 +75,6 @@ import static org.mockito.Mockito.verifyNoMoreInteractions; @ExtendWith(MockitoExtension.class) public class BearerTokenAuthenticationFilterTests { - private static final String TEST_TOKEN = "token"; - @Mock AuthenticationEntryPoint authenticationEntryPoint; @@ -95,9 +93,6 @@ public class BearerTokenAuthenticationFilterTests { @Mock AuthenticationDetailsSource authenticationDetailsSource; - @Mock - AuthenticationConverter authenticationConverter; - MockHttpServletRequest request; MockHttpServletResponse response; @@ -269,6 +264,24 @@ public class BearerTokenAuthenticationFilterTests { assertThat(error.getDescription()).isEqualTo("Invalid bearer token"); } + @Test + public void doFilterWhenSetAuthenticationConverterAndAuthenticationDetailsSourceThenIllegalArgument( + @Mock AuthenticationConverter authenticationConverter) { + BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager, + authenticationConverter); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> filter.setAuthenticationDetailsSource(this.authenticationDetailsSource)); + } + + @Test + public void doFilterWhenSetBearerTokenResolverAndAuthenticationConverterThenIllegalArgument( + @Mock AuthenticationConverter authenticationConverter) { + BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager, + authenticationConverter); + assertThatExceptionOfType(IllegalArgumentException.class) + .isThrownBy(() -> filter.setBearerTokenResolver(this.bearerTokenResolver)); + } + @Test public void setAuthenticationEntryPointWhenNullThenThrowsException() { BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager); @@ -302,9 +315,8 @@ public class BearerTokenAuthenticationFilterTests { @Test public void setConverterWhenNullThenThrowsException() { // @formatter:off - BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager); assertThatIllegalArgumentException() - .isThrownBy(() -> filter.setAuthenticationConverter(null)) + .isThrownBy(() -> new BearerTokenAuthenticationFilter(this.authenticationManager, null)) .withMessageContaining("authenticationConverter cannot be null"); // @formatter:on } @@ -327,171 +339,6 @@ public class BearerTokenAuthenticationFilterTests { // @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); @@ -506,10 +353,4 @@ public class BearerTokenAuthenticationFilterTests { verifyNoMoreInteractions(this.authenticationManager); } - private BearerTokenAuthenticationFilter addMocksWithConverter(BearerTokenAuthenticationFilter filter) { - filter.setAuthenticationEntryPoint(this.authenticationEntryPoint); - filter.setAuthenticationConverter(this.authenticationConverter); - return filter; - } - }