Skip to content

Commit e39e608

Browse files
Add support BearerTokenAuthenticationConverter
Closes gh-14750 Signed-off-by: Max Batischev <[email protected]>
1 parent e569c7a commit e39e608

File tree

9 files changed

+807
-63
lines changed

9 files changed

+807
-63
lines changed

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

Lines changed: 74 additions & 25 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,13 +69,13 @@
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

7076
/**
71-
*
7277
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
73-
*
78+
* <p>
7479
* By default, this wires a {@link BearerTokenAuthenticationFilter}, which can be used to
7580
* parse the request for bearer tokens and make an authentication attempt.
7681
*
@@ -84,6 +89,8 @@
8489
* authentication failures are handled
8590
* <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
8691
* bearer token from the request</li>
92+
* <li>{@link #authenticationConverter(AuthenticationConverter)} - customizes how to
93+
* convert a request to authentication</li>
8794
* <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
8895
* <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
8996
* </ul>
@@ -96,7 +103,7 @@
96103
* <li>supply a {@link JwtDecoder} instance via {@link JwtConfigurer#decoder}, or</li>
97104
* <li>expose a {@link JwtDecoder} bean</li>
98105
* </ul>
99-
*
106+
* <p>
100107
* Also with {@link #jwt(Customizer)} consider
101108
*
102109
* <ul>
@@ -111,7 +118,7 @@
111118
* </p>
112119
*
113120
* <h2>Security Filters</h2>
114-
*
121+
* <p>
115122
* The following {@code Filter}s are populated when {@link #jwt(Customizer)} is
116123
* configured:
117124
*
@@ -120,15 +127,15 @@
120127
* </ul>
121128
*
122129
* <h2>Shared Objects Created</h2>
123-
*
130+
* <p>
124131
* The following shared objects are populated:
125132
*
126133
* <ul>
127134
* <li>{@link SessionCreationPolicy} (optional)</li>
128135
* </ul>
129136
*
130137
* <h2>Shared Objects Used</h2>
131-
*
138+
* <p>
132139
* The following shared objects are used:
133140
*
134141
* <ul>
@@ -156,7 +163,7 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
156163

157164
private AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver;
158165

159-
private BearerTokenResolver bearerTokenResolver;
166+
private AuthenticationConverter authenticationConverter;
160167

161168
private JwtConfigurer jwtConfigurer;
162169

@@ -194,9 +201,16 @@ public OAuth2ResourceServerConfigurer<H> authenticationManagerResolver(
194201
return this;
195202
}
196203

204+
@Deprecated
197205
public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
198206
Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
199-
this.bearerTokenResolver = bearerTokenResolver;
207+
this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver);
208+
return this;
209+
}
210+
211+
public OAuth2ResourceServerConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) {
212+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
213+
this.authenticationConverter = authenticationConverter;
200214
return this;
201215
}
202216

@@ -271,16 +285,16 @@ public void init(H http) {
271285

272286
@Override
273287
public void configure(H http) {
274-
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
275-
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
276288
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
277289
if (resolver == null) {
278290
AuthenticationManager authenticationManager = getAuthenticationManager(http);
279291
resolver = (request) -> authenticationManager;
280292
}
281293

282294
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
283-
filter.setBearerTokenResolver(bearerTokenResolver);
295+
AuthenticationConverter converter = getAuthenticationConverter();
296+
this.requestMatcher.setAuthenticationConverter(converter);
297+
filter.setAuthenticationConverter(converter);
284298
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
285299
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
286300
filter = postProcess(filter);
@@ -363,16 +377,29 @@ AuthenticationManager getAuthenticationManager(H http) {
363377
return http.getSharedObject(AuthenticationManager.class);
364378
}
365379

380+
AuthenticationConverter getAuthenticationConverter() {
381+
if (this.authenticationConverter != null) {
382+
return this.authenticationConverter;
383+
}
384+
if (this.context.getBeanNamesForType(BearerTokenAuthenticationConverter.class).length > 0) {
385+
this.authenticationConverter = this.context.getBean(BearerTokenAuthenticationConverter.class);
386+
}
387+
else if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
388+
BearerTokenResolver bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
389+
this.authenticationConverter = new BearerTokenResolverAuthenticationConverterAdapter(bearerTokenResolver);
390+
}
391+
else {
392+
this.authenticationConverter = new BearerTokenAuthenticationConverter();
393+
}
394+
return this.authenticationConverter;
395+
}
396+
366397
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-
}
398+
AuthenticationConverter authenticationConverter = getAuthenticationConverter();
399+
if (authenticationConverter instanceof BearerTokenResolverAuthenticationConverterAdapter bearer) {
400+
return bearer.bearerTokenResolver;
374401
}
375-
return this.bearerTokenResolver;
402+
return null;
376403
}
377404

378405
public class JwtConfigurer {
@@ -560,21 +587,43 @@ AuthenticationManager getAuthenticationManager(H http) {
560587

561588
private static final class BearerTokenRequestMatcher implements RequestMatcher {
562589

563-
private BearerTokenResolver bearerTokenResolver;
590+
private AuthenticationConverter authenticationConverter;
564591

565592
@Override
566593
public boolean matches(HttpServletRequest request) {
567594
try {
568-
return this.bearerTokenResolver.resolve(request) != null;
595+
return this.authenticationConverter.convert(request) != null;
569596
}
570597
catch (OAuth2AuthenticationException ex) {
571598
return false;
572599
}
573600
}
574601

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

580629
}

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

Lines changed: 57 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-2024 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.
@@ -43,9 +43,10 @@
4343
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
4444
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
4545
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
46-
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
4746
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
47+
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationConverter;
4848
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
49+
import org.springframework.security.web.authentication.AuthenticationConverter;
4950
import org.springframework.security.web.util.matcher.RequestMatcher;
5051
import org.springframework.util.Assert;
5152
import org.springframework.util.StringUtils;
@@ -64,10 +65,14 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa
6465

6566
static final String BEARER_TOKEN_RESOLVER_REF = "bearer-token-resolver-ref";
6667

68+
static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";
69+
6770
static final String ENTRY_POINT_REF = "entry-point-ref";
6871

6972
static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver";
7073

74+
static final String AUTHENTICATION_CONVERTER = "authenticationConverter";
75+
7176
static final String AUTHENTICATION_ENTRY_POINT = "authenticationEntryPoint";
7277

7378
private final BeanReference authenticationManager;
@@ -124,25 +129,40 @@ public BeanDefinition parse(Element oauth2ResourceServer, ParserContext pc) {
124129
pc.getReaderContext().registerWithGeneratedName(opaqueTokenAuthenticationProvider)));
125130
}
126131
BeanMetadataElement bearerTokenResolver = getBearerTokenResolver(oauth2ResourceServer);
127-
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
128-
.rootBeanDefinition(BearerTokenRequestMatcher.class);
129-
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
130-
BeanDefinition requestMatcher = requestMatcherBuilder.getBeanDefinition();
132+
BeanMetadataElement authenticationConverter = getAuthenticationConverter(oauth2ResourceServer);
131133
BeanMetadataElement authenticationEntryPoint = getEntryPoint(oauth2ResourceServer);
134+
BeanDefinition requestMatcher = buildRequestMatcher(bearerTokenResolver, authenticationConverter);
132135
this.entryPoints.put(requestMatcher, authenticationEntryPoint);
133136
this.deniedHandlers.put(requestMatcher, this.accessDeniedHandler);
134137
this.ignoreCsrfRequestMatchers.add(requestMatcher);
135138
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder
136139
.rootBeanDefinition(BearerTokenAuthenticationFilter.class);
137140
BeanMetadataElement authenticationManagerResolver = getAuthenticationManagerResolver(oauth2ResourceServer);
138141
filterBuilder.addConstructorArgValue(authenticationManagerResolver);
139-
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
142+
filterBuilder.addPropertyValue(AUTHENTICATION_CONVERTER, authenticationConverter);
140143
filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint);
141144
filterBuilder.addPropertyValue("securityContextHolderStrategy",
142145
this.authenticationFilterSecurityContextHolderStrategy);
146+
if (bearerTokenResolver != null) {
147+
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
148+
}
143149
return filterBuilder.getBeanDefinition();
144150
}
145151

152+
private BeanDefinition buildRequestMatcher(BeanMetadataElement bearerTokenResolver,
153+
BeanMetadataElement authenticationConverter) {
154+
if (bearerTokenResolver != null) {
155+
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
156+
.rootBeanDefinition(BearerTokenRequestMatcher.class);
157+
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
158+
return requestMatcherBuilder.getBeanDefinition();
159+
}
160+
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
161+
.rootBeanDefinition(BearerTokenAuthenticationRequestMatcher.class);
162+
requestMatcherBuilder.addConstructorArgValue(authenticationConverter);
163+
return requestMatcherBuilder.getBeanDefinition();
164+
}
165+
146166
void validateConfiguration(Element oauth2ResourceServer, Element jwt, Element opaqueToken, ParserContext pc) {
147167
if (!oauth2ResourceServer.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)) {
148168
if (jwt == null && opaqueToken == null) {
@@ -178,11 +198,19 @@ BeanMetadataElement getAuthenticationManagerResolver(Element element) {
178198
BeanMetadataElement getBearerTokenResolver(Element element) {
179199
String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF);
180200
if (!StringUtils.hasLength(bearerTokenResolverRef)) {
181-
return new RootBeanDefinition(DefaultBearerTokenResolver.class);
201+
return null;
182202
}
183203
return new RuntimeBeanReference(bearerTokenResolverRef);
184204
}
185205

206+
BeanMetadataElement getAuthenticationConverter(Element element) {
207+
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
208+
if (!StringUtils.hasLength(authenticationConverterRef)) {
209+
return new RootBeanDefinition(BearerTokenAuthenticationConverter.class);
210+
}
211+
return new RuntimeBeanReference(authenticationConverterRef);
212+
}
213+
186214
BeanMetadataElement getEntryPoint(Element element) {
187215
String entryPointRef = element.getAttribute(ENTRY_POINT_REF);
188216
if (!StringUtils.hasLength(entryPointRef)) {
@@ -366,4 +394,25 @@ public boolean matches(HttpServletRequest request) {
366394

367395
}
368396

397+
static final class BearerTokenAuthenticationRequestMatcher implements RequestMatcher {
398+
399+
private final AuthenticationConverter authenticationConverter;
400+
401+
BearerTokenAuthenticationRequestMatcher(AuthenticationConverter authenticationConverter) {
402+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
403+
this.authenticationConverter = authenticationConverter;
404+
}
405+
406+
@Override
407+
public boolean matches(HttpServletRequest request) {
408+
try {
409+
return this.authenticationConverter.convert(request) != null;
410+
}
411+
catch (OAuth2AuthenticationException ex) {
412+
return false;
413+
}
414+
}
415+
416+
}
417+
369418
}

0 commit comments

Comments
 (0)