Skip to content

Commit 4967f3f

Browse files
franticticktickjzheaux
authored andcommitted
Add Support BearerTokenAuthenticationConverter
Closes gh-14750 Signed-off-by: Max Batischev <[email protected]>
1 parent 3f0326d commit 4967f3f

File tree

15 files changed

+721
-57
lines changed

15 files changed

+721
-57
lines changed

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

Lines changed: 76 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,8 @@
2323
import java.util.function.Supplier;
2424

2525
import jakarta.servlet.http.HttpServletRequest;
26+
import org.apache.commons.logging.Log;
27+
import org.apache.commons.logging.LogFactory;
2628

2729
import org.springframework.context.ApplicationContext;
2830
import org.springframework.core.convert.converter.Converter;
@@ -37,10 +39,12 @@
3739
import org.springframework.security.config.annotation.web.configurers.CsrfConfigurer;
3840
import org.springframework.security.config.annotation.web.configurers.ExceptionHandlingConfigurer;
3941
import org.springframework.security.config.http.SessionCreationPolicy;
42+
import org.springframework.security.core.Authentication;
4043
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
4144
import org.springframework.security.oauth2.jwt.Jwt;
4245
import org.springframework.security.oauth2.jwt.JwtDecoder;
4346
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
47+
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationToken;
4448
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
4549
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
4650
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
@@ -49,13 +53,14 @@
4953
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
5054
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
5155
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
52-
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
5356
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
57+
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter;
5458
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
5559
import org.springframework.security.web.AuthenticationEntryPoint;
5660
import org.springframework.security.web.access.AccessDeniedHandler;
5761
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
5862
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
63+
import org.springframework.security.web.authentication.AuthenticationConverter;
5964
import org.springframework.security.web.csrf.CsrfException;
6065
import org.springframework.security.web.util.matcher.AndRequestMatcher;
6166
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@@ -64,6 +69,7 @@
6469
import org.springframework.security.web.util.matcher.RequestHeaderRequestMatcher;
6570
import org.springframework.security.web.util.matcher.RequestMatcher;
6671
import org.springframework.util.Assert;
72+
import org.springframework.util.StringUtils;
6773
import org.springframework.web.accept.ContentNegotiationStrategy;
6874
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
6975

@@ -156,7 +162,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
156162

157163
private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
158164

159-
private BearerTokenResolver bearerTokenResolver;
165+
private AuthenticationConverter authenticationConverter;
160166

161167
private JwtConfigurer jwtConfigurer;
162168

@@ -194,9 +200,25 @@ public OAuth2ResourceServerConfigurer<H> authenticationManagerResolver(
194200
return this;
195201
}
196202

203+
/**
204+
* @deprecated please use {@link #authenticationConverter} instead
205+
*/
206+
@Deprecated
197207
public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
198208
Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
199-
this.bearerTokenResolver = bearerTokenResolver;
209+
this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver);
210+
return this;
211+
}
212+
213+
/**
214+
* Sets the {@link AuthenticationConverter} to use.
215+
* @param authenticationConverter the authentication converter
216+
* @return the {@link OAuth2ResourceServerConfigurer} for further configuration
217+
* @since 6.5
218+
*/
219+
public OAuth2ResourceServerConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) {
220+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
221+
this.authenticationConverter = authenticationConverter;
200222
return this;
201223
}
202224

@@ -271,16 +293,16 @@ public void init(H http) {
271293

272294
@Override
273295
public void configure(H http) {
274-
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
275-
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
276296
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
277297
if (resolver == null) {
278298
AuthenticationManager authenticationManager = getAuthenticationManager(http);
279299
resolver = (request) -> authenticationManager;
280300
}
281301

282302
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
283-
filter.setBearerTokenResolver(bearerTokenResolver);
303+
AuthenticationConverter converter = getAuthenticationConverter();
304+
this.requestMatcher.setAuthenticationConverter(converter);
305+
filter.setAuthenticationConverter(converter);
284306
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
285307
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
286308
filter = postProcess(filter);
@@ -363,16 +385,29 @@ AuthenticationManager getAuthenticationManager(H http) {
363385
return http.getSharedObject(AuthenticationManager.class);
364386
}
365387

388+
AuthenticationConverter getAuthenticationConverter() {
389+
if (this.authenticationConverter != null) {
390+
return this.authenticationConverter;
391+
}
392+
if (this.context.getBeanNamesForType(AuthenticationConverter.class).length > 0) {
393+
this.authenticationConverter = this.context.getBean(AuthenticationConverter.class);
394+
}
395+
else if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
396+
BearerTokenResolver bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
397+
this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver);
398+
}
399+
else {
400+
this.authenticationConverter = new BearerTokenAuthenticationConverter();
401+
}
402+
return this.authenticationConverter;
403+
}
404+
366405
BearerTokenResolver getBearerTokenResolver() {
367-
if (this.bearerTokenResolver == null) {
368-
if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
369-
this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
370-
}
371-
else {
372-
this.bearerTokenResolver = new DefaultBearerTokenResolver();
373-
}
406+
AuthenticationConverter authenticationConverter = getAuthenticationConverter();
407+
if (authenticationConverter instanceof BearerTokenResolverAuthenticationConverterAdapter bearer) {
408+
return bearer.bearerTokenResolver;
374409
}
375-
return this.bearerTokenResolver;
410+
return null;
376411
}
377412

378413
public class JwtConfigurer {
@@ -560,21 +595,43 @@ AuthenticationManager getAuthenticationManager(H http) {
560595

561596
private static final class BearerTokenRequestMatcher implements RequestMatcher {
562597

563-
private BearerTokenResolver bearerTokenResolver;
598+
private AuthenticationConverter authenticationConverter;
564599

565600
@Override
566601
public boolean matches(HttpServletRequest request) {
567602
try {
568-
return this.bearerTokenResolver.resolve(request) != null;
603+
return this.authenticationConverter.convert(request) != null;
569604
}
570605
catch (OAuth2AuthenticationException ex) {
571606
return false;
572607
}
573608
}
574609

575-
void setBearerTokenResolver(BearerTokenResolver tokenResolver) {
576-
Assert.notNull(tokenResolver, "resolver cannot be null");
577-
this.bearerTokenResolver = tokenResolver;
610+
void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
611+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
612+
this.authenticationConverter = authenticationConverter;
613+
}
614+
615+
}
616+
617+
private static final class BearerTokenResolverAuthenticationConverterAdapter implements AuthenticationConverter {
618+
619+
private final Log logger = LogFactory.getLog(BearerTokenResolverAuthenticationConverterAdapter.class);
620+
621+
private final BearerTokenResolver bearerTokenResolver;
622+
623+
BearerTokenResolverAuthenticationConverterAdapter(BearerTokenResolver bearerTokenResolver) {
624+
this.bearerTokenResolver = bearerTokenResolver;
625+
}
626+
627+
@Override
628+
public Authentication convert(HttpServletRequest request) {
629+
String token = this.bearerTokenResolver.resolve(request);
630+
if (!StringUtils.hasText(token)) {
631+
this.logger.trace("Did not process request since did not find bearer token");
632+
return null;
633+
}
634+
return new BearerTokenAuthenticationToken(token);
578635
}
579636

580637
}

config/src/main/java/org/springframework/security/config/http/OAuth2ResourceServerBeanDefinitionParser.java

Lines changed: 68 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2022 the original author or authors.
2+
* Copyright 2002-2025 the original author or authors.
33
*
44
* Licensed under the Apache License, Version 2.0 (the "License");
55
* you may not use this file except in compliance with the License.
@@ -23,6 +23,7 @@
2323
import org.w3c.dom.Element;
2424

2525
import org.springframework.beans.BeanMetadataElement;
26+
import org.springframework.beans.factory.BeanDefinitionStoreException;
2627
import org.springframework.beans.factory.FactoryBean;
2728
import org.springframework.beans.factory.config.BeanDefinition;
2829
import org.springframework.beans.factory.config.BeanReference;
@@ -43,9 +44,10 @@
4344
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
4445
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
4546
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
46-
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
4747
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
48+
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter;
4849
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
50+
import org.springframework.security.web.authentication.AuthenticationConverter;
4951
import org.springframework.security.web.util.matcher.RequestMatcher;
5052
import org.springframework.util.Assert;
5153
import org.springframework.util.StringUtils;
@@ -64,10 +66,14 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
6466

6567
static final String BEARER_TOKEN_RESOLVER_REF = "bearer-token-resolver-ref";
6668

69+
static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
70+
6771
static final String ENTRY_POINT_REF = "entry-point-ref";
6872

6973
static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver";
7074

75+
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
76+
7177
static final String AUTHENTICATION_ENTRY_POINT = "authenticationEntryPoint";
7278

7379
private final BeanReference authenticationManager;
@@ -124,25 +130,50 @@ public BeanDefinition parse(Element oauth2ResourceServer, ParserContext pc) {
124130
pc.getReaderContext().registerWithGeneratedName(opaqueTokenAuthenticationProvider)));
125131
}
126132
BeanMetadataElement bearerTokenResolver = getBearerTokenResolver(oauth2ResourceServer);
127-
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
128-
.rootBeanDefinition(BearerTokenRequestMatcher.class);
129-
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
130-
BeanDefinition requestMatcher = requestMatcherBuilder.getBeanDefinition();
133+
BeanMetadataElement authenticationConverter = getAuthenticationConverter(oauth2ResourceServer);
134+
if (bearerTokenResolver != null && authenticationConverter != null) {
135+
throw new BeanDefinitionStoreException(
136+
"You cannot use bearer-token-ref and authentication-converter-ref in the same oauth2-resource-server element");
137+
}
138+
if (bearerTokenResolver == null && authenticationConverter == null) {
139+
authenticationConverter = new RootBeanDefinition(BearerTokenAuthenticationConverter.class);
140+
}
131141
BeanMetadataElement authenticationEntryPoint = getEntryPoint(oauth2ResourceServer);
142+
BeanDefinition requestMatcher = buildRequestMatcher(bearerTokenResolver, authenticationConverter);
132143
this.entryPoints.put(requestMatcher, authenticationEntryPoint);
133144
this.deniedHandlers.put(requestMatcher, this.accessDeniedHandler);
134145
this.ignoreCsrfRequestMatchers.add(requestMatcher);
135146
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder
136147
.rootBeanDefinition(BearerTokenAuthenticationFilter.class);
137148
BeanMetadataElement authenticationManagerResolver = getAuthenticationManagerResolver(oauth2ResourceServer);
138149
filterBuilder.addConstructorArgValue(authenticationManagerResolver);
139-
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
140150
filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint);
141151
filterBuilder.addPropertyValue("securityContextHolderStrategy",
142152
this.authenticationFilterSecurityContextHolderStrategy);
153+
154+
if (authenticationConverter != null) {
155+
filterBuilder.addPropertyValue(AUTHENTICATION_CONVERTER, authenticationConverter);
156+
}
157+
if (bearerTokenResolver != null) {
158+
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
159+
}
143160
return filterBuilder.getBeanDefinition();
144161
}
145162

163+
private BeanDefinition buildRequestMatcher(BeanMetadataElement bearerTokenResolver,
164+
BeanMetadataElement authenticationConverter) {
165+
if (bearerTokenResolver != null) {
166+
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
167+
.rootBeanDefinition(BearerTokenRequestMatcher.class);
168+
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
169+
return requestMatcherBuilder.getBeanDefinition();
170+
}
171+
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
172+
.rootBeanDefinition(BearerTokenAuthenticationRequestMatcher.class);
173+
requestMatcherBuilder.addConstructorArgValue(authenticationConverter);
174+
return requestMatcherBuilder.getBeanDefinition();
175+
}
176+
146177
void validateConfiguration(Element oauth2ResourceServer, Element jwt, Element opaqueToken, ParserContext pc) {
147178
if (!oauth2ResourceServer.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)) {
148179
if (jwt == null && opaqueToken == null) {
@@ -178,11 +209,19 @@ BeanMetadataElement getAuthenticationManagerResolver(Element element) {
178209
BeanMetadataElement getBearerTokenResolver(Element element) {
179210
String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF);
180211
if (!StringUtils.hasLength(bearerTokenResolverRef)) {
181-
return new RootBeanDefinition(DefaultBearerTokenResolver.class);
212+
return null;
182213
}
183214
return new RuntimeBeanReference(bearerTokenResolverRef);
184215
}
185216

217+
BeanMetadataElement getAuthenticationConverter(Element element) {
218+
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
219+
if (!StringUtils.hasLength(authenticationConverterRef)) {
220+
return null;
221+
}
222+
return new RuntimeBeanReference(authenticationConverterRef);
223+
}
224+
186225
BeanMetadataElement getEntryPoint(Element element) {
187226
String entryPointRef = element.getAttribute(ENTRY_POINT_REF);
188227
if (!StringUtils.hasLength(entryPointRef)) {
@@ -366,4 +405,25 @@ public boolean matches(HttpServletRequest request) {
366405

367406
}
368407

408+
static final class BearerTokenAuthenticationRequestMatcher implements RequestMatcher {
409+
410+
private final AuthenticationConverter authenticationConverter;
411+
412+
BearerTokenAuthenticationRequestMatcher(AuthenticationConverter authenticationConverter) {
413+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
414+
this.authenticationConverter = authenticationConverter;
415+
}
416+
417+
@Override
418+
public boolean matches(HttpServletRequest request) {
419+
try {
420+
return this.authenticationConverter.convert(request) != null;
421+
}
422+
catch (OAuth2AuthenticationException ex) {
423+
return false;
424+
}
425+
}
426+
427+
}
428+
369429
}

config/src/main/resources/org/springframework/security/config/spring-security-6.5.rnc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -650,6 +650,9 @@ oauth2-resource-server.attlist &=
650650
oauth2-resource-server.attlist &=
651651
## Reference to a AuthenticationEntryPoint
652652
attribute entry-point-ref {xsd:token}?
653+
oauth2-resource-server.attlist &=
654+
## Reference to a AuthenticationConverter
655+
attribute authentication-converter-ref {xsd:token}?
653656

654657
jwt =
655658
## Configures JWT authentication

config/src/main/resources/org/springframework/security/config/spring-security-6.5.xsd

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1999,6 +1999,12 @@
19991999
</xs:documentation>
20002000
</xs:annotation>
20012001
</xs:attribute>
2002+
<xs:attribute name="authentication-converter-ref" type="xs:token">
2003+
<xs:annotation>
2004+
<xs:documentation>Reference to a AuthenticationConverter
2005+
</xs:documentation>
2006+
</xs:annotation>
2007+
</xs:attribute>
20022008
</xs:attributeGroup>
20032009
<xs:element name="jwt">
20042010
<xs:annotation>

0 commit comments

Comments
 (0)