Add Support BearerTokenAuthenticationConverter

Closes gh-14750

Signed-off-by: Max Batischev <mblancer@mail.ru>
This commit is contained in:
Max Batischev
2025-04-11 15:26:00 +03:00
committed by Josh Cummings
parent 3f0326d3f1
commit 4967f3feee
15 changed files with 722 additions and 58 deletions

View File

@@ -127,12 +127,14 @@ import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthen
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.BearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.provisioning.InMemoryUserDetailsManager;
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;
@@ -759,13 +761,6 @@ public class OAuth2ResourceServerConfigurerTests {
assertThat(oauth2.getBearerTokenResolver()).isEqualTo(resolver);
}
@Test
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
}
@Test
public void requestWhenCustomAuthenticationDetailsSourceThenUsed() throws Exception {
this.spring.register(CustomAuthenticationDetailsSource.class, JwtDecoderConfig.class, BasicController.class)
@@ -1415,6 +1410,47 @@ public class OAuth2ResourceServerConfigurerTests {
verify(authenticationConverter).convert(any(), any());
}
@Test
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.getAuthenticationConverter()).isInstanceOf(BearerTokenAuthenticationConverter.class);
}
private static <T> void registerMockBean(GenericApplicationContext context, String name, Class<T> clazz) {
context.registerBean(name, clazz, () -> mock(clazz));
}
@@ -2516,6 +2552,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 MultipleIssuersConfig {

View File

@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2025 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.
@@ -25,7 +25,6 @@ import java.time.Instant;
import java.time.ZoneId;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.stream.Collectors;
@@ -50,13 +49,11 @@ import org.junit.jupiter.api.extension.ExtendWith;
import org.mockito.Mockito;
import org.w3c.dom.Element;
import org.springframework.beans.BeanMetadataElement;
import org.springframework.beans.factory.BeanDefinitionStoreException;
import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.FactoryBean;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.config.BeanReference;
import org.springframework.beans.factory.parsing.BeanDefinitionParsingException;
import org.springframework.beans.factory.support.RootBeanDefinition;
import org.springframework.beans.factory.xml.BeanDefinitionParserDelegate;
import org.springframework.beans.factory.xml.ParserContext;
import org.springframework.beans.factory.xml.XmlReaderContext;
@@ -85,12 +82,14 @@ import org.springframework.security.oauth2.jwt.JwtClaimNames;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.introspection.OpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.test.context.annotation.SecurityTestExecutionListeners;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.test.context.junit.jupiter.SpringExtension;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.MvcResult;
@@ -462,6 +461,24 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
verify(bearerTokenResolver).resolve(any(HttpServletRequest.class));
}
@Test
public void getWhenCustomAuthenticationConverterThenUses() throws Exception {
this.spring
.configLocations(xml("MockAuthenticationConverter"), xml("MockJwtDecoder"), xml("AuthenticationConverter"))
.autowire();
JwtDecoder decoder = this.spring.getContext().getBean(JwtDecoder.class);
given(decoder.decode("token")).willReturn(TestJwts.jwt().build());
AuthenticationConverter authenticationConverter = this.spring.getContext()
.getBean(AuthenticationConverter.class);
given(authenticationConverter.convert(any(HttpServletRequest.class)))
.willReturn(new BearerTokenAuthenticationToken("token"));
this.mvc.perform(get("/")).andExpect(status().isNotFound());
verify(decoder).decode("token");
verify(authenticationConverter).convert(any(HttpServletRequest.class));
}
@Test
public void requestWhenBearerTokenResolverAllowsRequestBodyThenEitherHeaderOrRequestBodyIsAccepted()
throws Exception {
@@ -521,14 +538,6 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
// @formatter:on
}
@Test
public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed() {
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);
}
@Test
public void requestWhenCustomJwtDecoderThenUsed() throws Exception {
this.spring.configLocations(xml("MockJwtDecoder"), xml("Jwt")).autowire();
@@ -545,6 +554,12 @@ public class OAuth2ResourceServerBeanDefinitionParserTests {
.isThrownBy(() -> this.spring.configLocations(xml("JwtDecoderAndJwkSetUri")).autowire());
}
@Test
public void configureWhenAuthenticationConverterAndJwkSetUriThenException() {
assertThatExceptionOfType(BeanDefinitionStoreException.class).isThrownBy(
() -> this.spring.configLocations(xml("AuthenticationConverterAndBearerTokenResolver")).autowire());
}
@Test
public void requestWhenRealmNameConfiguredThenUsesOnUnauthenticated() throws Exception {
this.spring.configLocations(xml("MockJwtDecoder"), xml("AuthenticationEntryPoint")).autowire();