Add support BearerTokenAuthenticationConverter

Closes gh-14750
This commit is contained in:
Max Batischev 2024-04-08 00:55:56 +03:00
parent c8e5fbf21b
commit 3317b0d9b8
8 changed files with 759 additions and 54 deletions

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -41,6 +41,7 @@ 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.BearerTokenAuthenticationConverter;
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;
@ -49,13 +50,13 @@ import org.springframework.security.oauth2.server.resource.introspection.OpaqueT
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
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;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@ -68,9 +69,8 @@ import org.springframework.web.accept.ContentNegotiationStrategy;
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
/**
*
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
*
* <p>
* By default, this wires a {@link BearerTokenAuthenticationFilter}, which can be used to
* parse the request for bearer tokens and make an authentication attempt.
*
@ -84,6 +84,8 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
* authentication failures are handled
* <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
* bearer token from the request</li>
* <li>{@link #authenticationConverter(AuthenticationConverter)} - customizes how to
* convert a request to authentication</li>
* <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
* <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
* </ul>
@ -96,7 +98,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
* <li>supply a {@link JwtDecoder} instance via {@link JwtConfigurer#decoder}, or</li>
* <li>expose a {@link JwtDecoder} bean</li>
* </ul>
*
* <p>
* Also with {@link #jwt(Customizer)} consider
*
* <ul>
@ -111,7 +113,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
* </p>
*
* <h2>Security Filters</h2>
*
* <p>
* The following {@code Filter}s are populated when {@link #jwt(Customizer)} is
* configured:
*
@ -120,7 +122,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* <p>
* The following shared objects are populated:
*
* <ul>
@ -128,7 +130,7 @@ import org.springframework.web.accept.HeaderContentNegotiationStrategy;
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* <p>
* The following shared objects are used:
*
* <ul>
@ -156,6 +158,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private BearerTokenResolver bearerTokenResolver;
private AuthenticationConverter authenticationConverter;
private JwtConfigurer jwtConfigurer;
private OpaqueTokenConfigurer opaqueTokenConfigurer;
@ -198,6 +202,12 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
return this;
}
public OAuth2ResourceServerConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
return this;
}
/**
* @deprecated For removal in 7.0. Use {@link #jwt(Customizer)} or
* {@code jwt(Customizer.withDefaults())} to stick with defaults. See the <a href=
@ -269,16 +279,25 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
@Override
public void configure(H http) {
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
if (resolver == null) {
AuthenticationManager authenticationManager = getAuthenticationManager(http);
resolver = (request) -> authenticationManager;
}
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
filter.setBearerTokenResolver(bearerTokenResolver);
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
if (bearerTokenResolver != null) {
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
filter.setBearerTokenResolver(bearerTokenResolver);
}
else {
AuthenticationConverter converter = getAuthenticationConverter();
this.requestMatcher.setAuthenticationConverter(converter);
filter.setAuthenticationConverter(converter);
}
filter.setAuthenticationConverter(getAuthenticationConverter());
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
filter = postProcess(filter);
@ -366,13 +385,22 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
}
else {
this.bearerTokenResolver = new DefaultBearerTokenResolver();
}
}
return this.bearerTokenResolver;
}
AuthenticationConverter getAuthenticationConverter() {
if (this.authenticationConverter == null) {
if (this.context.getBeanNamesForType(AuthenticationConverter.class).length > 0) {
this.authenticationConverter = this.context.getBean(AuthenticationConverter.class);
}
else {
this.authenticationConverter = new BearerTokenAuthenticationConverter();
}
}
return this.authenticationConverter;
}
public class JwtConfigurer {
private final ApplicationContext context;
@ -560,10 +588,15 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
private BearerTokenResolver bearerTokenResolver;
private AuthenticationConverter authenticationConverter;
@Override
public boolean matches(HttpServletRequest request) {
try {
return this.bearerTokenResolver.resolve(request) != null;
if (this.bearerTokenResolver != null) {
return this.bearerTokenResolver.resolve(request) != null;
}
return this.authenticationConverter.convert(request) != null;
}
catch (OAuth2AuthenticationException ex) {
return false;
@ -575,6 +608,11 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
this.bearerTokenResolver = tokenResolver;
}
void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -37,15 +37,16 @@ import org.springframework.security.config.Elements;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.jwt.JwtDecoder;
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.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
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;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
@ -64,10 +65,14 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
static final String BEARER_TOKEN_RESOLVER_REF = "bearer-token-resolver-ref";
static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
static final String ENTRY_POINT_REF = "entry-point-ref";
static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver";
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
static final String AUTHENTICATION_ENTRY_POINT = "authenticationEntryPoint";
private final BeanReference authenticationManager;
@ -124,11 +129,9 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
pc.getReaderContext().registerWithGeneratedName(opaqueTokenAuthenticationProvider)));
}
BeanMetadataElement bearerTokenResolver = getBearerTokenResolver(oauth2ResourceServer);
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
BeanDefinition requestMatcher = requestMatcherBuilder.getBeanDefinition();
BeanMetadataElement authenticationConverter = getAuthenticationConverter(oauth2ResourceServer);
BeanMetadataElement authenticationEntryPoint = getEntryPoint(oauth2ResourceServer);
BeanDefinition requestMatcher = buildRequestMatcher(bearerTokenResolver, authenticationConverter);
this.entryPoints.put(requestMatcher, authenticationEntryPoint);
this.deniedHandlers.put(requestMatcher, this.accessDeniedHandler);
this.ignoreCsrfRequestMatchers.add(requestMatcher);
@ -136,13 +139,30 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
.rootBeanDefinition(BearerTokenAuthenticationFilter.class);
BeanMetadataElement authenticationManagerResolver = getAuthenticationManagerResolver(oauth2ResourceServer);
filterBuilder.addConstructorArgValue(authenticationManagerResolver);
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
filterBuilder.addPropertyValue(AUTHENTICATION_CONVERTER, authenticationConverter);
filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint);
filterBuilder.addPropertyValue("securityContextHolderStrategy",
this.authenticationFilterSecurityContextHolderStrategy);
if (bearerTokenResolver != null) {
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
}
return filterBuilder.getBeanDefinition();
}
private BeanDefinition buildRequestMatcher(BeanMetadataElement bearerTokenResolver,
BeanMetadataElement authenticationConverter) {
if (bearerTokenResolver != null) {
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
return requestMatcherBuilder.getBeanDefinition();
}
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenAuthenticationRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(authenticationConverter);
return requestMatcherBuilder.getBeanDefinition();
}
void validateConfiguration(Element oauth2ResourceServer, Element jwt, Element opaqueToken, ParserContext pc) {
if (!oauth2ResourceServer.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)) {
if (jwt == null && opaqueToken == null) {
@ -178,11 +198,19 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
BeanMetadataElement getBearerTokenResolver(Element element) {
String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF);
if (!StringUtils.hasLength(bearerTokenResolverRef)) {
return new RootBeanDefinition(DefaultBearerTokenResolver.class);
return null;
}
return new RuntimeBeanReference(bearerTokenResolverRef);
}
BeanMetadataElement getAuthenticationConverter(Element element) {
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
if (!StringUtils.hasLength(authenticationConverterRef)) {
return new RootBeanDefinition(BearerTokenAuthenticationConverter.class);
}
return new RuntimeBeanReference(authenticationConverterRef);
}
BeanMetadataElement getEntryPoint(Element element) {
String entryPointRef = element.getAttribute(ENTRY_POINT_REF);
if (!StringUtils.hasLength(entryPointRef)) {
@ -366,4 +394,25 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
}
static final class BearerTokenAuthenticationRequestMatcher implements RequestMatcher {
private final AuthenticationConverter authenticationConverter;
BearerTokenAuthenticationRequestMatcher(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
@Override
public boolean matches(HttpServletRequest request) {
try {
return this.authenticationConverter.convert(request) != null;
}
catch (OAuth2AuthenticationException ex) {
return false;
}
}
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -117,6 +117,7 @@ import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.TestJwts;
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.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
@ -133,6 +134,7 @@ import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.SecurityFilterChain;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
import org.springframework.test.web.servlet.ResultMatcher;
@ -760,10 +762,44 @@ public class OAuth2ResourceServerConfigurerTests {
}
@Test
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
public void getAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
AuthenticationConverter converter = mock(AuthenticationConverter.class);
AuthenticationConverter converterBean = mock(AuthenticationConverter.class);
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean("converterOne", AuthenticationConverter.class, () -> converterBean);
context.registerBean("converterTwo", AuthenticationConverter.class, () -> converterBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.authenticationConverter(converter);
assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter);
}
@Test
public void getAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
AuthenticationConverter converter = mock(AuthenticationConverter.class);
AuthenticationConverter converterBean = mock(AuthenticationConverter.class);
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean(AuthenticationConverter.class, () -> converterBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.authenticationConverter(converter);
assertThat(oauth2.getAuthenticationConverter()).isEqualTo(converter);
}
@Test
public void getAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() {
assertThatExceptionOfType(BeanCreationException.class)
.isThrownBy(
() -> this.spring.register(MultipleAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class)
.autowire())
.withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
}
@Test
public void getAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
assertThat(oauth2.getAuthenticationConverter()).isInstanceOf(BearerTokenAuthenticationConverter.class);
}
@Test
@ -1999,6 +2035,39 @@ public class OAuth2ResourceServerConfigurerTests {
}
@Configuration
@EnableWebSecurity
static class MultipleAuthenticationConverterBeansConfig {
@Bean
SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.anyRequest().authenticated()
.and()
.oauth2ResourceServer()
.jwt();
return http.build();
// @formatter:on
}
@Bean
AuthenticationConverter authenticationConverterOne() {
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
converter.setAllowUriQueryParameter(true);
return converter;
}
@Bean
AuthenticationConverter authenticationConverterTwo() {
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
converter.setAllowUriQueryParameter(true);
return converter;
}
}
@Configuration
@EnableWebSecurity
static class MultipleBearerTokenResolverBeansConfig {
@ -2048,6 +2117,7 @@ public class OAuth2ResourceServerConfigurerTests {
)
.oauth2ResourceServer((oauth2) -> oauth2
.jwt(withDefaults())
.bearerTokenResolver(new DefaultBearerTokenResolver())
.withObjectPostProcessor(new ObjectPostProcessor<BearerTokenAuthenticationFilter>() {
@Override
public BearerTokenAuthenticationFilter postProcess(BearerTokenAuthenticationFilter object) {

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -522,11 +522,11 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
}
@Test
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
public void getAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
OAuth2ResourceServerBeanDefinitionParser oauth2 = new OAuth2ResourceServerBeanDefinitionParser(
mock(BeanReference.class), mock(List.class), mock(Map.class), mock(Map.class), mock(List.class),
mock(BeanMetadataElement.class));
assertThat(oauth2.getBearerTokenResolver(mock(Element.class))).isInstanceOf(RootBeanDefinition.class);
assertThat(oauth2.getAuthenticationConverter(mock(Element.class))).isInstanceOf(RootBeanDefinition.class);
}
@Test

View File

@ -0,0 +1,180 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.resource.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.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
/**
* Implementation of {@link AuthenticationConverter}, that converts request to
* {@link BearerTokenAuthenticationToken}
*
* @author Max Batischev
* @since 6.3
*/
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;
@Override
public Authentication convert(HttpServletRequest request) {
String token = resolveToken(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;
}
/**
* 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;
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2021 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -33,25 +33,28 @@ import org.springframework.security.core.context.SecurityContext;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.context.SecurityContextHolderStrategy;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
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.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationEntryPointFailureHandler;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
import org.springframework.web.filter.OncePerRequestFilter;
/**
* Authenticates requests that contain an OAuth 2.0
* <a href="https://tools.ietf.org/html/rfc6750#section-1.2" target="_blank">Bearer
* Token</a>.
*
* <p>
* This filter should be wired with an {@link AuthenticationManager} that can authenticate
* a {@link BearerTokenAuthenticationToken}.
*
@ -76,12 +79,14 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
private AuthenticationFailureHandler authenticationFailureHandler = new AuthenticationEntryPointFailureHandler(
(request, response, exception) -> this.authenticationEntryPoint.commence(request, response, exception));
private BearerTokenResolver bearerTokenResolver = new DefaultBearerTokenResolver();
private BearerTokenResolver bearerTokenResolver;
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
@ -114,27 +119,35 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain)
throws ServletException, IOException {
String token;
Authentication authentication;
try {
token = this.bearerTokenResolver.resolve(request);
if (this.bearerTokenResolver != null) {
String token = this.bearerTokenResolver.resolve(request);
if (!StringUtils.hasText(token)) {
this.logger.trace("Did not process request since did not find bearer token");
return;
}
authentication = bearerTokenAuthenticationToken(token, request);
}
else {
authentication = this.authenticationConverter.convert(request);
}
}
catch (OAuth2AuthenticationException invalid) {
this.logger.trace("Sending to authentication entry point since failed to resolve bearer token", invalid);
this.authenticationEntryPoint.commence(request, response, invalid);
return;
}
if (token == null) {
this.logger.trace("Did not process request since did not find bearer token");
if (authentication == null) {
this.logger.trace("Failed to convert authentication request");
filterChain.doFilter(request, response);
return;
}
BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
try {
AuthenticationManager authenticationManager = this.authenticationManagerResolver.resolve(request);
Authentication authenticationResult = authenticationManager.authenticate(authenticationRequest);
Authentication authenticationResult = authenticationManager.authenticate(authentication);
SecurityContext context = this.securityContextHolderStrategy.createEmptyContext();
context.setAuthentication(authenticationResult);
this.securityContextHolderStrategy.setContext(context);
@ -151,6 +164,12 @@ public class BearerTokenAuthenticationFilter extends OncePerRequestFilter {
}
}
private Authentication bearerTokenAuthenticationToken(String token, HttpServletRequest request) {
BearerTokenAuthenticationToken authenticationToken = new BearerTokenAuthenticationToken(token);
authenticationToken.setDetails(this.authenticationDetailsSource.buildDetails(request));
return authenticationToken;
}
/**
* Sets the {@link SecurityContextHolderStrategy} to use. The default action is to use
* the {@link SecurityContextHolderStrategy} stored in {@link SecurityContextHolder}.
@ -208,13 +227,26 @@ 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
*/
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
Assert.notNull(this.bearerTokenResolver,
"bearerTokenResolver cannot be null, required authenticationDetailsSource must be set to authenticationConverter");
this.authenticationDetailsSource = authenticationDetailsSource;
}
/**
* Set the {@link AuthenticationConverter} to use. Defaults to
* {@link BearerTokenAuthenticationConverter}.
* @param authenticationConverter the {@code AuthenticationConverter} to use
* @since 6.3
*/
public void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}
}

View File

@ -0,0 +1,148 @@
/*
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package org.springframework.security.oauth2.server.resource.authentication;
import jakarta.servlet.http.HttpServletRequest;
import org.junit.jupiter.api.Test;
import org.mockito.Mockito;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.http.MediaType;
import org.springframework.mock.web.MockHttpServletRequest;
import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.core.Authentication;
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
import static org.assertj.core.api.Assertions.assertThat;
import static org.assertj.core.api.Assertions.assertThatExceptionOfType;
import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.verify;
/**
* Tests for {@link BearerTokenAuthenticationConverter}
*
* @author Max Batischev
*/
public class BearerTokenAuthenticationConverterTests {
private static final String X_AUTH_TOKEN_HEADER = "X-Auth-Token";
private static final String TEST_X_AUTH_TOKEN = "test-x-auth-token";
private static final String BEARER_TOKEN = "test_bearer_token";
private final BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
@Test
public void convertWhenAuthorizationHeaderIsPresentThenTokenIsConverted() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN);
Authentication authentication = this.converter.convert(request);
assertThat(authentication).isNotNull();
}
@Test
public void convertWhenQueryParameterIsPresentThenTokenIsConverted() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod(HttpMethod.GET.name());
request.addParameter("access_token", BEARER_TOKEN);
this.converter.setAllowUriQueryParameter(true);
Authentication authentication = this.converter.convert(request);
assertThat(authentication).isNotNull();
}
@Test
public void convertWhenAuthorizationHeaderNotIsPresentThenTokenIsNotConverted() {
MockHttpServletRequest request = new MockHttpServletRequest();
Authentication authentication = this.converter.convert(request);
assertThat(authentication).isNull();
}
@Test
public void convertWhenAuthorizationHeaderIsPresentTogetherWithQueryParameterThenAuthenticationExceptionIsThrown() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addParameter("access_token", BEARER_TOKEN);
request.setMethod(HttpMethod.GET.name());
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN);
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request))
.withMessageContaining("Found multiple bearer tokens in the request");
}
@Test
public void convertWhenXAuthTokenHeaderIsPresentAndBearerTokenHeaderNameSetThenTokenIsConverted() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(X_AUTH_TOKEN_HEADER, "Bearer " + TEST_X_AUTH_TOKEN);
this.converter.setBearerTokenHeaderName(X_AUTH_TOKEN_HEADER);
Authentication authentication = this.converter.convert(request);
assertThat(authentication).isNotNull();
}
@Test
public void convertWhenHeaderWithMissingTokenIsPresentThenAuthenticationExceptionIsThrown() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer ");
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(("Bearer token is malformed"));
}
@Test
public void convertWhenHeaderWithInvalidCharactersIsPresentThenAuthenticationExceptionIsThrown() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer an\"invalid\"token");
assertThatExceptionOfType(OAuth2AuthenticationException.class).isThrownBy(() -> this.converter.convert(request))
.withMessageContaining(("Bearer token is malformed"));
}
@Test
@SuppressWarnings("unchecked")
public void convertWhenCustomAuthenticationDetailsSourceSetThenTokenIsConverted() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.addHeader(HttpHeaders.AUTHORIZATION, "Bearer " + BEARER_TOKEN);
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = Mockito
.mock(AuthenticationDetailsSource.class);
this.converter.setAuthenticationDetailsSource(authenticationDetailsSource);
Authentication authentication = this.converter.convert(request);
verify(authenticationDetailsSource).buildDetails(any());
assertThat(authentication).isNotNull();
}
@Test
public void convertWhenFormParameterIsPresentAndAllowFormEncodedBodyParameterThenConverted() {
MockHttpServletRequest request = new MockHttpServletRequest();
request.setMethod(HttpMethod.POST.name());
request.setContentType(MediaType.APPLICATION_FORM_URLENCODED_VALUE);
request.addParameter("access_token", BEARER_TOKEN);
this.converter.setAllowFormEncodedBodyParameter(true);
assertThat(this.converter.convert(request)).isNotNull();
}
}

View File

@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@ -47,6 +47,7 @@ import org.springframework.security.oauth2.server.resource.InvalidBearerTokenExc
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.AuthenticationFailureHandler;
import org.springframework.security.web.context.RequestAttributeSecurityContextRepository;
import org.springframework.security.web.context.SecurityContextRepository;
@ -69,6 +70,8 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
@ExtendWith(MockitoExtension.class)
public class BearerTokenAuthenticationFilterTests {
private static final String TEST_TOKEN = "token";
@Mock
AuthenticationEntryPoint authenticationEntryPoint;
@ -78,6 +81,9 @@ public class BearerTokenAuthenticationFilterTests {
@Mock
AuthenticationManager authenticationManager;
@Mock
AuthenticationConverter authenticationConverter;
@Mock
AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
@ -102,7 +108,7 @@ public class BearerTokenAuthenticationFilterTests {
@Test
public void doFilterWhenBearerTokenPresentThenAuthenticates() throws ServletException, IOException {
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.doFilter(this.request, this.response, this.filterChain);
@ -117,12 +123,13 @@ public class BearerTokenAuthenticationFilterTests {
@Test
public void doFilterWhenSecurityContextRepositoryThenSaves() throws ServletException, IOException {
SecurityContextRepository securityContextRepository = mock(SecurityContextRepository.class);
String token = "token";
String token = TEST_TOKEN;
given(this.bearerTokenResolver.resolve(this.request)).willReturn(token);
TestingAuthenticationToken expectedAuthentication = new TestingAuthenticationToken("test", "password");
given(this.authenticationManager.authenticate(any())).willReturn(expectedAuthentication);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.setSecurityContextRepository(securityContextRepository);
filter.doFilter(this.request, this.response, this.filterChain);
ArgumentCaptor<BearerTokenAuthenticationToken> captor = ArgumentCaptor
@ -138,13 +145,13 @@ public class BearerTokenAuthenticationFilterTests {
public void doFilterWhenUsingAuthenticationManagerResolverThenAuthenticates() throws Exception {
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManagerResolver));
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManagerResolver.resolve(any())).willReturn(this.authenticationManager);
filter.doFilter(this.request, this.response, this.filterChain);
ArgumentCaptor<BearerTokenAuthenticationToken> captor = ArgumentCaptor
.forClass(BearerTokenAuthenticationToken.class);
verify(this.authenticationManager).authenticate(captor.capture());
assertThat(captor.getValue().getPrincipal()).isEqualTo("token");
assertThat(captor.getValue().getPrincipal()).isEqualTo(TEST_TOKEN);
assertThat(this.request.getAttribute(RequestAttributeSecurityContextRepository.DEFAULT_REQUEST_ATTR_NAME))
.isNotNull();
}
@ -171,7 +178,7 @@ public class BearerTokenAuthenticationFilterTests {
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED,
"description", "uri");
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error);
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@ -185,7 +192,7 @@ public class BearerTokenAuthenticationFilterTests {
BearerTokenError error = new BearerTokenError(BearerTokenErrorCodes.INVALID_TOKEN, HttpStatus.UNAUTHORIZED,
"description", "uri");
OAuth2AuthenticationException exception = new OAuth2AuthenticationException(error);
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any(BearerTokenAuthenticationToken.class))).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@ -197,7 +204,7 @@ public class BearerTokenAuthenticationFilterTests {
@Test
public void doFilterWhenAuthenticationServiceExceptionThenRethrows() {
AuthenticationServiceException exception = new AuthenticationServiceException("message");
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any())).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@ -208,7 +215,7 @@ public class BearerTokenAuthenticationFilterTests {
@Test
public void doFilterWhenCustomEntryPointAndAuthenticationErrorThenUses() throws ServletException, IOException {
AuthenticationException exception = new InvalidBearerTokenException("message");
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
given(this.authenticationManager.authenticate(any())).willThrow(exception);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
@ -220,7 +227,7 @@ public class BearerTokenAuthenticationFilterTests {
@Test
public void doFilterWhenCustomAuthenticationDetailsSourceThenUses() throws ServletException, IOException {
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
filter.doFilter(this.request, this.response, this.filterChain);
@ -229,7 +236,7 @@ public class BearerTokenAuthenticationFilterTests {
@Test
public void doFilterWhenCustomSecurityContextHolderStrategyThenUses() throws ServletException, IOException {
given(this.bearerTokenResolver.resolve(this.request)).willReturn("token");
given(this.bearerTokenResolver.resolve(this.request)).willReturn(TEST_TOKEN);
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));
SecurityContextHolderStrategy strategy = mock(SecurityContextHolderStrategy.class);
@ -260,7 +267,7 @@ public class BearerTokenAuthenticationFilterTests {
}
@Test
public void setAuthenticationConverterWhenNullThenThrowsException() {
public void setAuthenticationDetailsSourceWhenNullThenThrowsException() {
// @formatter:off
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager);
assertThatIllegalArgumentException()
@ -269,6 +276,16 @@ public class BearerTokenAuthenticationFilterTests {
// @formatter:on
}
@Test
public void setAuthenticationConverterWhenNullThenThrowsException() {
// @formatter:off
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(this.authenticationManager);
assertThatIllegalArgumentException()
.isThrownBy(() -> filter.setAuthenticationConverter(null))
.withMessageContaining("authenticationConverter cannot be null");
// @formatter:on
}
@Test
public void constructorWhenNullAuthenticationManagerThenThrowsException() {
// @formatter:off
@ -287,6 +304,171 @@ public 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);
@ -294,6 +476,12 @@ public class BearerTokenAuthenticationFilterTests {
return filter;
}
private BearerTokenAuthenticationFilter addMocksWithConverter(BearerTokenAuthenticationFilter filter) {
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter.setAuthenticationConverter(this.authenticationConverter);
return filter;
}
private void dontAuthenticate() throws ServletException, IOException {
BearerTokenAuthenticationFilter filter = addMocks(
new BearerTokenAuthenticationFilter(this.authenticationManager));