Polish BearerTokenAuthenticationConverter Support

- Moved to BearerTokenAuthenticationFilter constructor to align with
AuthenticationFilter
- Undeprecated BearerTokenResolver to reduce number of migration scenarios
- Updated to 7.0 schema
- Added migration docs

Issue gh-14750
This commit is contained in:
Josh Cummings 2025-06-04 13:35:31 -06:00
parent 30577bd291
commit eaab42a73c
14 changed files with 203 additions and 376 deletions

View File

@ -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<H extends HttpSecurityBuilder<
return this;
}
/**
* @deprecated please use {@link #authenticationConverter} instead
*/
@Deprecated
public OAuth2ResourceServerConfigurer<H> 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<H extends HttpSecurityBuilder<
* Sets the {@link AuthenticationConverter} to use.
* @param authenticationConverter the authentication converter
* @return the {@link OAuth2ResourceServerConfigurer} for further configuration
* @since 6.5
* @since 7.0
*/
public OAuth2ResourceServerConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
@ -299,10 +291,9 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
resolver = (request) -> 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<H extends HttpSecurityBuilder<
}
else if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 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<H extends HttpSecurityBuilder<
BearerTokenResolver getBearerTokenResolver() {
AuthenticationConverter authenticationConverter = getAuthenticationConverter();
if (authenticationConverter instanceof BearerTokenResolverAuthenticationConverterAdapter bearer) {
if (authenticationConverter instanceof OAuth2ResourceServerConfigurer.BearerTokenResolverHoldingAuthenticationConverter bearer) {
return bearer.bearerTokenResolver;
}
return null;
@ -614,24 +605,22 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
}
private static final class BearerTokenResolverAuthenticationConverterAdapter implements AuthenticationConverter {
private final Log logger = LogFactory.getLog(BearerTokenResolverAuthenticationConverterAdapter.class);
private static final class BearerTokenResolverHoldingAuthenticationConverter implements AuthenticationConverter {
private final BearerTokenResolver bearerTokenResolver;
BearerTokenResolverAuthenticationConverterAdapter(BearerTokenResolver bearerTokenResolver) {
private final AuthenticationConverter authenticationConverter;
BearerTokenResolverHoldingAuthenticationConverter(BearerTokenResolver bearerTokenResolver) {
this.bearerTokenResolver = bearerTokenResolver;
BearerTokenAuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
authenticationConverter.setBearerTokenResolver(bearerTokenResolver);
this.authenticationConverter = authenticationConverter;
}
@Override
public Authentication convert(HttpServletRequest request) {
String token = this.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);
return this.authenticationConverter.convert(request);
}
}

View File

@ -72,8 +72,6 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver";
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
static final String AUTHENTICATION_ENTRY_POINT = "authenticationEntryPoint";
private final BeanReference authenticationManager;
@ -152,7 +150,7 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
this.authenticationFilterSecurityContextHolderStrategy);
if (authenticationConverter != null) {
filterBuilder.addPropertyValue(AUTHENTICATION_CONVERTER, authenticationConverter);
filterBuilder.addConstructorArgValue(authenticationConverter);
}
if (bearerTokenResolver != null) {
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
@ -170,7 +168,9 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
}
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenAuthenticationRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(authenticationConverter);
if (authenticationConverter != null) {
requestMatcherBuilder.addConstructorArgValue(authenticationConverter);
}
return requestMatcherBuilder.getBeanDefinition();
}
@ -409,6 +409,10 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
private final AuthenticationConverter authenticationConverter;
BearerTokenAuthenticationRequestMatcher() {
this.authenticationConverter = new BearerTokenAuthenticationConverter();
}
BearerTokenAuthenticationRequestMatcher(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;

View File

@ -650,9 +650,6 @@ 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

View File

@ -1999,12 +1999,6 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authentication-converter-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to a AuthenticationConverter
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="jwt">
<xs:annotation>

View File

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

View File

@ -1999,6 +1999,12 @@
</xs:documentation>
</xs:annotation>
</xs:attribute>
<xs:attribute name="authentication-converter-ref" type="xs:token">
<xs:annotation>
<xs:documentation>Reference to a AuthenticationConverter
</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:attributeGroup>
<xs:element name="jwt">
<xs:annotation>

View File

@ -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;
}
}

View File

@ -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)
----
======

View File

@ -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]]
== <jwt>

View File

@ -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 <a href="https://tools.ietf.org/html/rfc6750#section-2" target="_blank">RFC 6750
* Section 2: Authenticated Requests</a>
* @deprecated Use
* {@link org.springframework.security.web.authentication.AuthenticationConverter} instead
*/
@Deprecated
@FunctionalInterface
public interface BearerTokenResolver {

View File

@ -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<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
private static final Pattern authorizationPattern = Pattern.compile("^Bearer (?<token>[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;
}
/**

View File

@ -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<HttpServletRequest> 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<HttpServletRequest, ?> 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<HttpServletRequest> 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<HttpServletRequest> 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<HttpServletRequest, ?> 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;
}
}

View File

@ -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();
}

View File

@ -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<HttpServletRequest, ?> 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<BearerTokenAuthenticationToken> 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<BearerTokenAuthenticationToken> captor = ArgumentCaptor
.forClass(BearerTokenAuthenticationToken.class);
verify(this.authenticationManager).authenticate(captor.capture());
assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN);
ArgumentCaptor<SecurityContext> 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<BearerTokenAuthenticationToken> 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;
}
}