Skip to content

Commit 1e7c07f

Browse files
Max Batischevjzheaux
Max Batischev
authored andcommitted
Add support BearerTokenAuthenticationConverter
Closes gh-14750
1 parent e569c7a commit 1e7c07f

File tree

8 files changed

+757
-52
lines changed

8 files changed

+757
-52
lines changed

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

+52-14
Original file line numberDiff line numberDiff line change
@@ -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>
@@ -158,6 +160,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
158160

159161
private BearerTokenResolver bearerTokenResolver;
160162

163+
private AuthenticationConverter authenticationConverter;
164+
161165
private JwtConfigurer jwtConfigurer;
162166

163167
private OpaqueTokenConfigurer opaqueTokenConfigurer;
@@ -200,6 +204,12 @@ public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver
200204
return this;
201205
}
202206

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

272282
@Override
273283
public void configure(H http) {
274-
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
275-
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
276284
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
277285
if (resolver == null) {
278286
AuthenticationManager authenticationManager = getAuthenticationManager(http);
279287
resolver = (request) -> authenticationManager;
280288
}
281-
282289
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
283-
filter.setBearerTokenResolver(bearerTokenResolver);
290+
291+
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
292+
if (bearerTokenResolver != null) {
293+
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
294+
filter.setBearerTokenResolver(bearerTokenResolver);
295+
}
296+
else {
297+
AuthenticationConverter converter = getAuthenticationConverter();
298+
this.requestMatcher.setAuthenticationConverter(converter);
299+
filter.setAuthenticationConverter(converter);
300+
}
301+
302+
filter.setAuthenticationConverter(getAuthenticationConverter());
284303
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
285304
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
286305
filter = postProcess(filter);
@@ -368,11 +387,20 @@ BearerTokenResolver getBearerTokenResolver() {
368387
if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
369388
this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
370389
}
390+
}
391+
return this.bearerTokenResolver;
392+
}
393+
394+
AuthenticationConverter getAuthenticationConverter() {
395+
if (this.authenticationConverter == null) {
396+
if (this.context.getBeanNamesForType(AuthenticationConverter.class).length > 0) {
397+
this.authenticationConverter = this.context.getBean(AuthenticationConverter.class);
398+
}
371399
else {
372-
this.bearerTokenResolver = new DefaultBearerTokenResolver();
400+
this.authenticationConverter = new BearerTokenAuthenticationConverter();
373401
}
374402
}
375-
return this.bearerTokenResolver;
403+
return this.authenticationConverter;
376404
}
377405

378406
public class JwtConfigurer {
@@ -562,10 +590,15 @@ private static final class BearerTokenRequestMatcher implements RequestMatcher {
562590

563591
private BearerTokenResolver bearerTokenResolver;
564592

593+
private AuthenticationConverter authenticationConverter;
594+
565595
@Override
566596
public boolean matches(HttpServletRequest request) {
567597
try {
568-
return this.bearerTokenResolver.resolve(request) != null;
598+
if (this.bearerTokenResolver != null) {
599+
return this.bearerTokenResolver.resolve(request) != null;
600+
}
601+
return this.authenticationConverter.convert(request) != null;
569602
}
570603
catch (OAuth2AuthenticationException ex) {
571604
return false;
@@ -577,6 +610,11 @@ void setBearerTokenResolver(BearerTokenResolver tokenResolver) {
577610
this.bearerTokenResolver = tokenResolver;
578611
}
579612

613+
void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
614+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
615+
this.authenticationConverter = authenticationConverter;
616+
}
617+
580618
}
581619

582620
}

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

+57-8
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)