Skip to content

Commit 3317b0d

Browse files
Max BatischevMax Batischev
Max Batischev
authored and
Max Batischev
committed
Add support BearerTokenAuthenticationConverter
Closes gh-14750
1 parent c8e5fbf commit 3317b0d

File tree

8 files changed

+758
-53
lines changed

8 files changed

+758
-53
lines changed

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

Lines changed: 53 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2023 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.
@@ -41,6 +41,7 @@
4141
import org.springframework.security.oauth2.jwt.Jwt;
4242
import org.springframework.security.oauth2.jwt.JwtDecoder;
4343
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
44+
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
4445
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
4546
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
4647
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
@@ -49,13 +50,13 @@
4950
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
5051
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
5152
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
52-
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
5353
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
5454
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
5555
import org.springframework.security.web.AuthenticationEntryPoint;
5656
import org.springframework.security.web.access.AccessDeniedHandler;
5757
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
5858
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
59+
import org.springframework.security.web.authentication.AuthenticationConverter;
5960
import org.springframework.security.web.csrf.CsrfException;
6061
import org.springframework.security.web.util.matcher.AndRequestMatcher;
6162
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
@@ -68,9 +69,8 @@
6869
import org.springframework.web.accept.HeaderContentNegotiationStrategy;
6970

7071
/**
71-
*
7272
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
73-
*
73+
* <p>
7474
* By default, this wires a {@link BearerTokenAuthenticationFilter}, which can be used to
7575
* parse the request for bearer tokens and make an authentication attempt.
7676
*
@@ -84,6 +84,8 @@
8484
* authentication failures are handled
8585
* <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
8686
* bearer token from the request</li>
87+
* <li>{@link #authenticationConverter(AuthenticationConverter)} - customizes how to
88+
* convert a request to authentication</li>
8789
* <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
8890
* <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
8991
* </ul>
@@ -96,7 +98,7 @@
9698
* <li>supply a {@link JwtDecoder} instance via {@link JwtConfigurer#decoder}, or</li>
9799
* <li>expose a {@link JwtDecoder} bean</li>
98100
* </ul>
99-
*
101+
* <p>
100102
* Also with {@link #jwt(Customizer)} consider
101103
*
102104
* <ul>
@@ -111,7 +113,7 @@
111113
* </p>
112114
*
113115
* <h2>Security Filters</h2>
114-
*
116+
* <p>
115117
* The following {@code Filter}s are populated when {@link #jwt(Customizer)} is
116118
* configured:
117119
*
@@ -120,15 +122,15 @@
120122
* </ul>
121123
*
122124
* <h2>Shared Objects Created</h2>
123-
*
125+
* <p>
124126
* The following shared objects are populated:
125127
*
126128
* <ul>
127129
* <li>{@link SessionCreationPolicy} (optional)</li>
128130
* </ul>
129131
*
130132
* <h2>Shared Objects Used</h2>
131-
*
133+
* <p>
132134
* The following shared objects are used:
133135
*
134136
* <ul>
@@ -156,6 +158,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
156158

157159
private BearerTokenResolver bearerTokenResolver;
158160

161+
private AuthenticationConverter authenticationConverter;
162+
159163
private JwtConfigurer jwtConfigurer;
160164

161165
private OpaqueTokenConfigurer opaqueTokenConfigurer;
@@ -198,6 +202,12 @@ public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver
198202
return this;
199203
}
200204

205+
public OAuth2ResourceServerConfigurer<H> authenticationConverter(AuthenticationConverter authenticationConverter) {
206+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
207+
this.authenticationConverter = authenticationConverter;
208+
return this;
209+
}
210+
201211
/**
202212
* @deprecated For removal in 7.0. Use {@link #jwt(Customizer)} or
203213
* {@code jwt(Customizer.withDefaults())} to stick with defaults. See the <a href=
@@ -269,16 +279,25 @@ public void init(H http) {
269279

270280
@Override
271281
public void configure(H http) {
272-
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
273-
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
274282
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
275283
if (resolver == null) {
276284
AuthenticationManager authenticationManager = getAuthenticationManager(http);
277285
resolver = (request) -> authenticationManager;
278286
}
279-
280287
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
281-
filter.setBearerTokenResolver(bearerTokenResolver);
288+
289+
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
290+
if (bearerTokenResolver != null) {
291+
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
292+
filter.setBearerTokenResolver(bearerTokenResolver);
293+
}
294+
else {
295+
AuthenticationConverter converter = getAuthenticationConverter();
296+
this.requestMatcher.setAuthenticationConverter(converter);
297+
filter.setAuthenticationConverter(converter);
298+
}
299+
300+
filter.setAuthenticationConverter(getAuthenticationConverter());
282301
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
283302
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
284303
filter = postProcess(filter);
@@ -366,11 +385,20 @@ BearerTokenResolver getBearerTokenResolver() {
366385
if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
367386
this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
368387
}
388+
}
389+
return this.bearerTokenResolver;
390+
}
391+
392+
AuthenticationConverter getAuthenticationConverter() {
393+
if (this.authenticationConverter == null) {
394+
if (this.context.getBeanNamesForType(AuthenticationConverter.class).length > 0) {
395+
this.authenticationConverter = this.context.getBean(AuthenticationConverter.class);
396+
}
369397
else {
370-
this.bearerTokenResolver = new DefaultBearerTokenResolver();
398+
this.authenticationConverter = new BearerTokenAuthenticationConverter();
371399
}
372400
}
373-
return this.bearerTokenResolver;
401+
return this.authenticationConverter;
374402
}
375403

376404
public class JwtConfigurer {
@@ -560,10 +588,15 @@ private static final class BearerTokenRequestMatcher implements RequestMatcher {
560588

561589
private BearerTokenResolver bearerTokenResolver;
562590

591+
private AuthenticationConverter authenticationConverter;
592+
563593
@Override
564594
public boolean matches(HttpServletRequest request) {
565595
try {
566-
return this.bearerTokenResolver.resolve(request) != null;
596+
if (this.bearerTokenResolver != null) {
597+
return this.bearerTokenResolver.resolve(request) != null;
598+
}
599+
return this.authenticationConverter.convert(request) != null;
567600
}
568601
catch (OAuth2AuthenticationException ex) {
569602
return false;
@@ -575,6 +608,11 @@ void setBearerTokenResolver(BearerTokenResolver tokenResolver) {
575608
this.bearerTokenResolver = tokenResolver;
576609
}
577610

611+
void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
612+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
613+
this.authenticationConverter = authenticationConverter;
614+
}
615+
578616
}
579617

580618
}

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.
@@ -37,15 +37,16 @@
3737
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
3838
import org.springframework.security.oauth2.jwt.JwtDecoder;
3939
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
40+
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
4041
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
4142
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
4243
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
4344
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
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;
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)