Skip to content

Commit 31f310f

Browse files
thecodinglogjzheaux
authored andcommitted
Add BearerTokenAuthenticationConverter
BearerTokenAuthenticationConverter is introduced to solve the problem of not being able to change AuthenticationDetailsSource. BearerTokenAuthenticationFilter delegates to BearerTokenAuthenticationConverter the task of creating BearerTokenAuthenticationToken and setting AuthenticationDetailsSource. BearerTokenAuthenticationConverter is customizable and the customized converter can be used in BearerTokenAuthenticationFilter. Closes gh-8840
1 parent efb394d commit 31f310f

File tree

6 files changed

+333
-16
lines changed

6 files changed

+333
-16
lines changed

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

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
* Copyright 2002-2020 the original author or authors.
2+
* Copyright 2002-2021 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.
@@ -39,6 +39,7 @@
3939
import org.springframework.security.oauth2.jwt.Jwt;
4040
import org.springframework.security.oauth2.jwt.JwtDecoder;
4141
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
42+
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
4243
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
4344
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
4445
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
@@ -51,6 +52,7 @@
5152
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
5253
import org.springframework.security.web.AuthenticationEntryPoint;
5354
import org.springframework.security.web.access.AccessDeniedHandler;
55+
import org.springframework.security.web.authentication.AuthenticationConverter;
5456
import org.springframework.security.web.util.matcher.AndRequestMatcher;
5557
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
5658
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
@@ -78,6 +80,8 @@
7880
* authentication failures are handled
7981
* <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
8082
* bearer token from the request</li>
83+
* <li>{@link #bearerTokenAuthenticationConverter(AuthenticationConverter)}</li> -
84+
* customizes how to convert a bear token authentication from the request
8185
* <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
8286
* <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
8387
* </ul>
@@ -159,6 +163,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<
159163

160164
private BearerTokenRequestMatcher requestMatcher = new BearerTokenRequestMatcher();
161165

166+
private AuthenticationConverter authenticationConverter;
167+
162168
public OAuth2ResourceServerConfigurer(ApplicationContext context) {
163169
Assert.notNull(context, "context cannot be null");
164170
this.context = context;
@@ -189,6 +195,13 @@ public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver
189195
return this;
190196
}
191197

198+
public OAuth2ResourceServerConfigurer<H> bearerTokenAuthenticationConverter(
199+
AuthenticationConverter authenticationConverter) {
200+
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
201+
this.authenticationConverter = authenticationConverter;
202+
return this;
203+
}
204+
192205
public JwtConfigurer jwt() {
193206
if (this.jwtConfigurer == null) {
194207
this.jwtConfigurer = new JwtConfigurer(this.context);
@@ -252,8 +265,11 @@ public void configure(H http) {
252265
AuthenticationManager authenticationManager = getAuthenticationManager(http);
253266
resolver = (request) -> authenticationManager;
254267
}
268+
269+
this.authenticationConverter = getBearerTokenAuthenticationConverter();
270+
255271
BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
256-
filter.setBearerTokenResolver(bearerTokenResolver);
272+
filter.setAuthenticationConverter(this.authenticationConverter);
257273
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
258274
filter = postProcess(filter);
259275
http.addFilter(filter);
@@ -347,6 +363,20 @@ BearerTokenResolver getBearerTokenResolver() {
347363
return this.bearerTokenResolver;
348364
}
349365

366+
AuthenticationConverter getBearerTokenAuthenticationConverter() {
367+
if (this.authenticationConverter == null) {
368+
if (this.context.getBeanNamesForType(BearerTokenAuthenticationConverter.class).length > 0) {
369+
this.authenticationConverter = this.context.getBean(BearerTokenAuthenticationConverter.class);
370+
}
371+
else {
372+
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
373+
converter.setBearerTokenResolver(getBearerTokenResolver());
374+
this.authenticationConverter = converter;
375+
}
376+
}
377+
return this.authenticationConverter;
378+
}
379+
350380
public class JwtConfigurer {
351381

352382
private final ApplicationContext context;

config/src/test/java/org/springframework/security/config/annotation/web/configurers/oauth2/server/resource/OAuth2ResourceServerConfigurerTests.java

Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@
3333
import java.util.stream.Collectors;
3434

3535
import javax.annotation.PreDestroy;
36+
import javax.servlet.http.HttpServletRequest;
3637

3738
import com.nimbusds.jose.JWSAlgorithm;
3839
import com.nimbusds.jose.JWSHeader;
@@ -108,7 +109,9 @@
108109
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
109110
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
110111
import org.springframework.security.oauth2.jwt.TestJwts;
112+
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
111113
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
114+
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
112115
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
113116
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
114117
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
@@ -720,6 +723,72 @@ public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed()
720723
assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
721724
}
722725

726+
@Test
727+
public void getBearerTokenAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
728+
BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
729+
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
730+
GenericWebApplicationContext context = new GenericWebApplicationContext();
731+
context.registerBean("converterOne", BearerTokenAuthenticationConverter.class, () -> converterBean);
732+
context.registerBean("converterTwo", BearerTokenAuthenticationConverter.class, () -> converterBean);
733+
this.spring.context(context).autowire();
734+
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
735+
oauth2.bearerTokenAuthenticationConverter(converter);
736+
assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converter);
737+
}
738+
739+
@Test
740+
public void getBearerTokenAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() {
741+
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> this.spring
742+
.register(MultipleBearerTokenAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class)
743+
.autowire()).withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
744+
}
745+
746+
@Test
747+
public void getBearerTokenAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
748+
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
749+
BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
750+
GenericWebApplicationContext context = new GenericWebApplicationContext();
751+
context.registerBean(BearerTokenAuthenticationConverter.class, () -> converterBean);
752+
this.spring.context(context).autowire();
753+
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
754+
oauth2.bearerTokenAuthenticationConverter(converter);
755+
assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converter);
756+
}
757+
758+
@Test
759+
public void getBearerTokenAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
760+
ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
761+
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
762+
assertThat(oauth2.getBearerTokenAuthenticationConverter())
763+
.isInstanceOf(BearerTokenAuthenticationConverter.class);
764+
}
765+
766+
@Test
767+
public void getBearerTokenAuthenticationConverterWhenConverterBeanRegisteredThenBeanIsUsed() {
768+
BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
769+
GenericWebApplicationContext context = new GenericWebApplicationContext();
770+
context.registerBean(BearerTokenAuthenticationConverter.class, () -> converterBean);
771+
this.spring.context(context).autowire();
772+
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
773+
assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converterBean);
774+
775+
}
776+
777+
@Test
778+
public void getBearerTokenAuthenticationConverterWhenOnlyResolverBeanRegisteredThenUseTheResolver() {
779+
HttpServletRequest servletRequest = mock(HttpServletRequest.class);
780+
BearerTokenResolver resolverBean = (request) -> "bearer customToken";
781+
GenericWebApplicationContext context = new GenericWebApplicationContext();
782+
context.registerBean(BearerTokenResolver.class, () -> resolverBean);
783+
this.spring.context(context).autowire();
784+
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
785+
BearerTokenAuthenticationToken bearerTokenAuthenticationToken = (BearerTokenAuthenticationToken) oauth2
786+
.getBearerTokenAuthenticationConverter().convert(servletRequest);
787+
String token = bearerTokenAuthenticationToken.getToken();
788+
assertThat(token).isEqualTo("bearer customToken");
789+
790+
}
791+
723792
@Test
724793
public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception {
725794
this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire();
@@ -1871,6 +1940,32 @@ BearerTokenResolver resolverTwo() {
18711940

18721941
}
18731942

1943+
@EnableWebSecurity
1944+
static class MultipleBearerTokenAuthenticationConverterBeansConfig extends WebSecurityConfigurerAdapter {
1945+
1946+
@Override
1947+
protected void configure(HttpSecurity http) throws Exception {
1948+
// @formatter:off
1949+
http
1950+
.oauth2ResourceServer()
1951+
.jwt();
1952+
// @formatter:on
1953+
}
1954+
1955+
@Bean
1956+
BearerTokenAuthenticationConverter converterOne() {
1957+
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
1958+
return converter;
1959+
}
1960+
1961+
@Bean
1962+
BearerTokenAuthenticationConverter converterTwo() {
1963+
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
1964+
return converter;
1965+
}
1966+
1967+
}
1968+
18741969
@EnableWebSecurity
18751970
static class CustomJwtDecoderOnDsl extends WebSecurityConfigurerAdapter {
18761971

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
/*
2+
* Copyright 2002-2021 the original author or authors.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* https://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
17+
package org.springframework.security.oauth2.server.resource.authentication;
18+
19+
import javax.servlet.http.HttpServletRequest;
20+
21+
import org.springframework.security.authentication.AuthenticationDetailsSource;
22+
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
23+
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
24+
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
25+
import org.springframework.security.web.authentication.AuthenticationConverter;
26+
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
27+
import org.springframework.util.Assert;
28+
29+
/**
30+
* Converts from a HttpServletRequest to {@link BearerTokenAuthenticationToken} that can
31+
* be authenticated. Null authentication possible if there was no Authorization header
32+
* with Bearer Token.
33+
*
34+
* @author Jeongjin Kim
35+
* @since 5.5
36+
*/
37+
public final class BearerTokenAuthenticationConverter implements AuthenticationConverter {
38+
39+
private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();
40+
41+
private BearerTokenResolver bearerTokenResolver;
42+
43+
public BearerTokenAuthenticationConverter() {
44+
this.bearerTokenResolver = new DefaultBearerTokenResolver();
45+
}
46+
47+
@Override
48+
public BearerTokenAuthenticationToken convert(HttpServletRequest request) {
49+
String token = this.bearerTokenResolver.resolve(request);
50+
51+
if (token == null) {
52+
return null;
53+
}
54+
55+
BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
56+
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
57+
return authenticationRequest;
58+
}
59+
60+
/**
61+
* Set the {@link BearerTokenResolver} to use. Defaults to
62+
* {@link DefaultBearerTokenResolver}.
63+
* @param bearerTokenResolver the {@code BearerTokenResolver} to use
64+
*/
65+
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
66+
Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
67+
this.bearerTokenResolver = bearerTokenResolver;
68+
}
69+
70+
/**
71+
* Set the {@link AuthenticationDetailsSource} to use. Defaults to
72+
* {@link WebAuthenticationDetailsSource}.
73+
* @param authenticationDetailsSource the {@code AuthenticationDetailsSource} to use
74+
*/
75+
public void setAuthenticationDetailsSource(
76+
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
77+
Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
78+
this.authenticationDetailsSource = authenticationDetailsSource;
79+
}
80+
81+
}

0 commit comments

Comments
 (0)