Prechádzať zdrojové kódy

Add BearerTokenAuthenticationConverter

BearerTokenAuthenticationConverter is introduced to solve the
problem of not being able to change AuthenticationDetailsSource.
BearerTokenAuthenticationFilter delegates to
BearerTokenAuthenticationConverter the task of creating
BearerTokenAuthenticationToken and setting AuthenticationDetailsSource.
BearerTokenAuthenticationConverter is customizable and the customized
converter can be used in BearerTokenAuthenticationFilter.

Closes gh-8840
Jeongjin Kim 5 rokov pred
rodič
commit
31f310fd22

+ 32 - 2
config/src/main/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurer.java

@@ -1,5 +1,5 @@
 /*
 /*
- * Copyright 2002-2020 the original author or authors.
+ * Copyright 2002-2021 the original author or authors.
  *
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
  * you may not use this file except in compliance with the License.
@@ -39,6 +39,7 @@ import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.Jwt;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
 import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
 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.JwtAuthenticationProvider;
 import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
 import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
@@ -51,6 +52,7 @@ import org.springframework.security.oauth2.server.resource.web.DefaultBearerToke
 import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
 import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.AuthenticationEntryPoint;
 import org.springframework.security.web.access.AccessDeniedHandler;
 import org.springframework.security.web.access.AccessDeniedHandler;
+import org.springframework.security.web.authentication.AuthenticationConverter;
 import org.springframework.security.web.util.matcher.AndRequestMatcher;
 import org.springframework.security.web.util.matcher.AndRequestMatcher;
 import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
 import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
 import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
 import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
@@ -78,6 +80,8 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
  * authentication failures are handled
  * authentication failures are handled
  * <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
  * <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
  * bearer token from the request</li>
  * bearer token from the request</li>
+ * <li>{@link #bearerTokenAuthenticationConverter(AuthenticationConverter)}</li> -
+ * customizes how to convert a bear token authentication from the request
  * <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
  * <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
  * <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
  * <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
  * </ul>
  * </ul>
@@ -159,6 +163,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 
 
 	private BearerTokenRequestMatcher requestMatcher = new BearerTokenRequestMatcher();
 	private BearerTokenRequestMatcher requestMatcher = new BearerTokenRequestMatcher();
 
 
+	private AuthenticationConverter authenticationConverter;
+
 	public OAuth2ResourceServerConfigurer(ApplicationContext context) {
 	public OAuth2ResourceServerConfigurer(ApplicationContext context) {
 		Assert.notNull(context, "context cannot be null");
 		Assert.notNull(context, "context cannot be null");
 		this.context = context;
 		this.context = context;
@@ -189,6 +195,13 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 		return this;
 		return this;
 	}
 	}
 
 
+	public OAuth2ResourceServerConfigurer<H> bearerTokenAuthenticationConverter(
+			AuthenticationConverter authenticationConverter) {
+		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
+		this.authenticationConverter = authenticationConverter;
+		return this;
+	}
+
 	public JwtConfigurer jwt() {
 	public JwtConfigurer jwt() {
 		if (this.jwtConfigurer == null) {
 		if (this.jwtConfigurer == null) {
 			this.jwtConfigurer = new JwtConfigurer(this.context);
 			this.jwtConfigurer = new JwtConfigurer(this.context);
@@ -252,8 +265,11 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 			AuthenticationManager authenticationManager = getAuthenticationManager(http);
 			AuthenticationManager authenticationManager = getAuthenticationManager(http);
 			resolver = (request) -> authenticationManager;
 			resolver = (request) -> authenticationManager;
 		}
 		}
+
+		this.authenticationConverter = getBearerTokenAuthenticationConverter();
+
 		BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
 		BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
-		filter.setBearerTokenResolver(bearerTokenResolver);
+		filter.setAuthenticationConverter(this.authenticationConverter);
 		filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
 		filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
 		filter = postProcess(filter);
 		filter = postProcess(filter);
 		http.addFilter(filter);
 		http.addFilter(filter);
@@ -347,6 +363,20 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
 		return this.bearerTokenResolver;
 		return this.bearerTokenResolver;
 	}
 	}
 
 
+	AuthenticationConverter getBearerTokenAuthenticationConverter() {
+		if (this.authenticationConverter == null) {
+			if (this.context.getBeanNamesForType(BearerTokenAuthenticationConverter.class).length > 0) {
+				this.authenticationConverter = this.context.getBean(BearerTokenAuthenticationConverter.class);
+			}
+			else {
+				BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
+				converter.setBearerTokenResolver(getBearerTokenResolver());
+				this.authenticationConverter = converter;
+			}
+		}
+		return this.authenticationConverter;
+	}
+
 	public class JwtConfigurer {
 	public class JwtConfigurer {
 
 
 		private final ApplicationContext context;
 		private final ApplicationContext context;

+ 95 - 0
config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

@@ -33,6 +33,7 @@ import java.util.Map;
 import java.util.stream.Collectors;
 import java.util.stream.Collectors;
 
 
 import javax.annotation.PreDestroy;
 import javax.annotation.PreDestroy;
+import javax.servlet.http.HttpServletRequest;
 
 
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSAlgorithm;
 import com.nimbusds.jose.JWSHeader;
 import com.nimbusds.jose.JWSHeader;
@@ -108,7 +109,9 @@ import org.springframework.security.oauth2.jwt.JwtDecoder;
 import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
 import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
 import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
 import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
 import org.springframework.security.oauth2.jwt.TestJwts;
 import org.springframework.security.oauth2.jwt.TestJwts;
+import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
 import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
 import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
 import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
 import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
@@ -720,6 +723,72 @@ public class OAuth2ResourceServerConfigurerTests {
 		assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
 		assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
 	}
 	}
 
 
+	@Test
+	public void getBearerTokenAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
+		BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
+		BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
+		GenericWebApplicationContext context = new GenericWebApplicationContext();
+		context.registerBean("converterOne", BearerTokenAuthenticationConverter.class, () -> converterBean);
+		context.registerBean("converterTwo", BearerTokenAuthenticationConverter.class, () -> converterBean);
+		this.spring.context(context).autowire();
+		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
+		oauth2.bearerTokenAuthenticationConverter(converter);
+		assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converter);
+	}
+
+	@Test
+	public void getBearerTokenAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() {
+		assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> this.spring
+				.register(MultipleBearerTokenAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class)
+				.autowire()).withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
+	}
+
+	@Test
+	public void getBearerTokenAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
+		BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
+		BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
+		GenericWebApplicationContext context = new GenericWebApplicationContext();
+		context.registerBean(BearerTokenAuthenticationConverter.class, () -> converterBean);
+		this.spring.context(context).autowire();
+		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
+		oauth2.bearerTokenAuthenticationConverter(converter);
+		assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converter);
+	}
+
+	@Test
+	public void getBearerTokenAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
+		ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
+		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
+		assertThat(oauth2.getBearerTokenAuthenticationConverter())
+				.isInstanceOf(BearerTokenAuthenticationConverter.class);
+	}
+
+	@Test
+	public void getBearerTokenAuthenticationConverterWhenConverterBeanRegisteredThenBeanIsUsed() {
+		BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
+		GenericWebApplicationContext context = new GenericWebApplicationContext();
+		context.registerBean(BearerTokenAuthenticationConverter.class, () -> converterBean);
+		this.spring.context(context).autowire();
+		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
+		assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converterBean);
+
+	}
+
+	@Test
+	public void getBearerTokenAuthenticationConverterWhenOnlyResolverBeanRegisteredThenUseTheResolver() {
+		HttpServletRequest servletRequest = mock(HttpServletRequest.class);
+		BearerTokenResolver resolverBean = (request) -> "bearer customToken";
+		GenericWebApplicationContext context = new GenericWebApplicationContext();
+		context.registerBean(BearerTokenResolver.class, () -> resolverBean);
+		this.spring.context(context).autowire();
+		OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
+		BearerTokenAuthenticationToken bearerTokenAuthenticationToken = (BearerTokenAuthenticationToken) oauth2
+				.getBearerTokenAuthenticationConverter().convert(servletRequest);
+		String token = bearerTokenAuthenticationToken.getToken();
+		assertThat(token).isEqualTo("bearer customToken");
+
+	}
+
 	@Test
 	@Test
 	public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception {
 	public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception {
 		this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire();
 		this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire();
@@ -1871,6 +1940,32 @@ public class OAuth2ResourceServerConfigurerTests {
 
 
 	}
 	}
 
 
+	@EnableWebSecurity
+	static class MultipleBearerTokenAuthenticationConverterBeansConfig extends WebSecurityConfigurerAdapter {
+
+		@Override
+		protected void configure(HttpSecurity http) throws Exception {
+			// @formatter:off
+			http
+				.oauth2ResourceServer()
+					.jwt();
+			// @formatter:on
+		}
+
+		@Bean
+		BearerTokenAuthenticationConverter converterOne() {
+			BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
+			return converter;
+		}
+
+		@Bean
+		BearerTokenAuthenticationConverter converterTwo() {
+			BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
+			return converter;
+		}
+
+	}
+
 	@EnableWebSecurity
 	@EnableWebSecurity
 	static class CustomJwtDecoderOnDsl extends WebSecurityConfigurerAdapter {
 	static class CustomJwtDecoderOnDsl extends WebSecurityConfigurerAdapter {
 
 

+ 81 - 0
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationConverter.java

@@ -0,0 +1,81 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.springframework.security.authentication.AuthenticationDetailsSource;
+import org.springframework.security.oauth2.server.resource.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;
+
+/**
+ * Converts from a HttpServletRequest to {@link BearerTokenAuthenticationToken} that can
+ * be authenticated. Null authentication possible if there was no Authorization header
+ * with Bearer Token.
+ *
+ * @author Jeongjin Kim
+ * @since 5.5
+ */
+public final class BearerTokenAuthenticationConverter implements AuthenticationConverter {
+
+	private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
+
+	private BearerTokenResolver bearerTokenResolver;
+
+	public BearerTokenAuthenticationConverter() {
+		this.bearerTokenResolver = new DefaultBearerTokenResolver();
+	}
+
+	@Override
+	public BearerTokenAuthenticationToken convert(HttpServletRequest request) {
+		String token = this.bearerTokenResolver.resolve(request);
+
+		if (token == null) {
+			return null;
+		}
+
+		BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
+		authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
+		return authenticationRequest;
+	}
+
+	/**
+	 * Set the {@link BearerTokenResolver} to use. Defaults to
+	 * {@link DefaultBearerTokenResolver}.
+	 * @param bearerTokenResolver the {@code BearerTokenResolver} to use
+	 */
+	public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
+		Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
+		this.bearerTokenResolver = bearerTokenResolver;
+	}
+
+	/**
+	 * Set the {@link AuthenticationDetailsSource} to use. Defaults to
+	 * {@link WebAuthenticationDetailsSource}.
+	 * @param authenticationDetailsSource the {@code AuthenticationDetailsSource} to use
+	 */
+	public void setAuthenticationDetailsSource(
+			AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
+		Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
+		this.authenticationDetailsSource = authenticationDetailsSource;
+	}
+
+}

+ 28 - 14
oauth2/oauth2-resource-server/src/main/java/org/springframework/security/oauth2/server/resource/web/BearerTokenAuthenticationFilter.java

@@ -24,7 +24,6 @@ import javax.servlet.http.HttpServletRequest;
 import javax.servlet.http.HttpServletResponse;
 import javax.servlet.http.HttpServletResponse;
 
 
 import org.springframework.core.log.LogMessage;
 import org.springframework.core.log.LogMessage;
-import org.springframework.security.authentication.AuthenticationDetailsSource;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManager;
 import org.springframework.security.authentication.AuthenticationManagerResolver;
 import org.springframework.security.authentication.AuthenticationManagerResolver;
 import org.springframework.security.authentication.AuthenticationServiceException;
 import org.springframework.security.authentication.AuthenticationServiceException;
@@ -32,12 +31,12 @@ import org.springframework.security.core.Authentication;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.AuthenticationException;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContext;
 import org.springframework.security.core.context.SecurityContextHolder;
 import org.springframework.security.core.context.SecurityContextHolder;
-import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
 import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
 import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
+import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
 import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
 import org.springframework.security.web.AuthenticationEntryPoint;
 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.authentication.AuthenticationFailureHandler;
-import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
 import org.springframework.util.Assert;
 import org.springframework.util.Assert;
 import org.springframework.web.filter.OncePerRequestFilter;
 import org.springframework.web.filter.OncePerRequestFilter;
 
 
@@ -61,10 +60,6 @@ public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter
 
 
 	private final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
 	private final AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
 
 
-	private final AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
-
-	private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
-
 	private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
 	private AuthenticationEntryPoint authenticationEntryPoint = new BearerTokenAuthenticationEntryPoint();
 
 
 	private AuthenticationFailureHandler authenticationFailureHandler = (request, response, exception) -> {
 	private AuthenticationFailureHandler authenticationFailureHandler = (request, response, exception) -> {
@@ -74,6 +69,8 @@ public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter
 		this.authenticationEntryPoint.commence(request, response, exception);
 		this.authenticationEntryPoint.commence(request, response, exception);
 	};
 	};
 
 
+	private AuthenticationConverter authenticationConverter = new BearerTokenAuthenticationConverter();
+
 	/**
 	/**
 	 * Construct a {@code BearerTokenAuthenticationFilter} using the provided parameter(s)
 	 * Construct a {@code BearerTokenAuthenticationFilter} using the provided parameter(s)
 	 * @param authenticationManagerResolver
 	 * @param authenticationManagerResolver
@@ -106,22 +103,21 @@ public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter
 	@Override
 	@Override
 	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 	protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
 			throws ServletException, IOException {
 			throws ServletException, IOException {
-		String token;
+		Authentication authenticationRequest;
 		try {
 		try {
-			token = this.bearerTokenResolver.resolve(request);
+			authenticationRequest = this.authenticationConverter.convert(request);
 		}
 		}
-		catch (OAuth2AuthenticationException invalid) {
+		catch (AuthenticationException invalid) {
 			this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid);
 			this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid);
 			this.authenticationEntryPoint.commence(request, response, invalid);
 			this.authenticationEntryPoint.commence(request, response, invalid);
 			return;
 			return;
 		}
 		}
-		if (token == null) {
+		if (authenticationRequest == null) {
 			this.logger.trace("Did not process request since did not find bearer token");
 			this.logger.trace("Did not process request since did not find bearer token");
 			filterChain.doFilter(request, response);
 			filterChain.doFilter(request, response);
 			return;
 			return;
 		}
 		}
-		BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
-		authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
+
 		try {
 		try {
 			AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
 			AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
 			Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
 			Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
@@ -144,10 +140,17 @@ public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter
 	 * Set the {@link BearerTokenResolver} to use. Defaults to
 	 * Set the {@link BearerTokenResolver} to use. Defaults to
 	 * {@link DefaultBearerTokenResolver}.
 	 * {@link DefaultBearerTokenResolver}.
 	 * @param bearerTokenResolver the {@code BearerTokenResolver} to use
 	 * @param bearerTokenResolver the {@code BearerTokenResolver} to use
+	 * @deprecated Instead, use {@link BearerTokenAuthenticationConverter} explicitly
+	 * @see BearerTokenAuthenticationConverter
 	 */
 	 */
+	@Deprecated
 	public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
 	public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
 		Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
 		Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
-		this.bearerTokenResolver = bearerTokenResolver;
+		Assert.isTrue(this.authenticationConverter instanceof BearerTokenAuthenticationConverter,
+				"bearerTokenResolver and authenticationConverter cannot both be customized in this filter. "
+						+ "Since you've customized the authenticationConverter, "
+						+ "please consider configuring the bearerTokenResolver there.");
+		((BearerTokenAuthenticationConverter) this.authenticationConverter).setBearerTokenResolver(bearerTokenResolver);
 	}
 	}
 
 
 	/**
 	/**
@@ -171,4 +174,15 @@ public final class BearerTokenAuthenticationFilter extends OncePerRequestFilter
 		this.authenticationFailureHandler = authenticationFailureHandler;
 		this.authenticationFailureHandler = authenticationFailureHandler;
 	}
 	}
 
 
+	/**
+	 * Set the {@link AuthenticationConverter} to use. Defaults to
+	 * {@link BearerTokenAuthenticationConverter}.
+	 * @param authenticationConverter the {@code AuthenticationConverter} to use
+	 * @since 5.5
+	 */
+	public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
+		Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
+		this.authenticationConverter = authenticationConverter;
+	}
+
 }
 }

+ 87 - 0
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/authentication/BearerTokenAuthenticationConverterTests.java

@@ -0,0 +1,87 @@
+/*
+ * Copyright 2002-2021 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.authentication;
+
+import javax.servlet.http.HttpServletRequest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.junit.MockitoJUnitRunner;
+
+import org.springframework.http.HttpHeaders;
+import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
+
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatIllegalArgumentException;
+import static org.mockito.BDDMockito.given;
+import static org.mockito.Mockito.mock;
+
+/**
+ * Tests for {@link BearerTokenAuthenticationConverter}
+ *
+ * @author Jeongjin Kim
+ * @since 5.5
+ */
+@RunWith(MockitoJUnitRunner.class)
+public class BearerTokenAuthenticationConverterTests {
+
+	private BearerTokenAuthenticationConverter converter;
+
+	@Before
+	public void setup() {
+		this.converter = new BearerTokenAuthenticationConverter();
+	}
+
+	@Test
+	public void setBearerTokenResolverWithNullThenThrowsException() {
+		// @formatter:off
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.converter.setBearerTokenResolver(null))
+				.withMessageContaining("bearerTokenResolver cannot be null");
+		// @formatter:on
+	}
+
+	@Test
+	public void setAuthenticationDetailsSourceWithNullThenThrowsException() {
+		// @formatter:off
+		assertThatIllegalArgumentException()
+				.isThrownBy(() -> this.converter.setAuthenticationDetailsSource(null))
+				.withMessageContaining("authenticationDetailsSource cannot be null");
+		// @formatter:on
+	}
+
+	@Test
+	public void convertWhenNoBearerTokenHeaderThenNull() {
+		HttpServletRequest request = mock(HttpServletRequest.class);
+
+		BearerTokenAuthenticationToken convert = this.converter.convert(request);
+
+		assertThat(convert).isNull();
+	}
+
+	@Test
+	public void convertWhenBearerTokenThenBearerTokenAuthenticationToken() {
+		HttpServletRequest request = mock(HttpServletRequest.class);
+		given(request.getHeader(HttpHeaders.AUTHORIZATION)).willReturn("Bearer token");
+
+		BearerTokenAuthenticationToken token = this.converter.convert(request);
+
+		assertThat(token.getToken()).isEqualTo("token");
+	}
+
+}

+ 10 - 0
oauth2/oauth2-resource-server/src/test/java/org/springframework/security/oauth2/server/resource/web/BearerTokenAuthenticationFilterTests.java

@@ -187,6 +187,16 @@ public class BearerTokenAuthenticationFilterTests {
 		// @formatter:on
 		// @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
 	@Test
 	public void constructorWhenNullAuthenticationManagerThenThrowsException() {
 	public void constructorWhenNullAuthenticationManagerThenThrowsException() {
 		// @formatter:off
 		// @formatter:off