Skip to content

Add BearerTokenAuthenticationConverter #8975

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Closed
wants to merge 1 commit into from
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2020 the original author or authors.
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
Expand Down Expand Up @@ -39,6 +39,7 @@
import org.springframework.security.oauth2.jwt.Jwt;
import org.springframework.security.oauth2.jwt.JwtDecoder;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationProvider;
import org.springframework.security.oauth2.server.resource.authentication.OpaqueTokenAuthenticationProvider;
Expand All @@ -51,6 +52,7 @@
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
import org.springframework.security.web.util.matcher.NegatedRequestMatcher;
Expand Down Expand Up @@ -78,6 +80,8 @@
* authentication failures are handled
* <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
* bearer token from the request</li>
* <li>{@link #bearerTokenAuthenticationConverter(AuthenticationConverter)}</li> -
* customizes how to convert a bear token authentication from the request
* <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
* <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
* </ul>
Expand Down Expand Up @@ -159,6 +163,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<

private BearerTokenRequestMatcher requestMatcher = new BearerTokenRequestMatcher();

private AuthenticationConverter authenticationConverter;

public OAuth2ResourceServerConfigurer(ApplicationContext context) {
Assert.notNull(context, "context cannot be null");
this.context = context;
Expand Down Expand Up @@ -189,6 +195,13 @@ public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver
return this;
}

public OAuth2ResourceServerConfigurer<H> bearerTokenAuthenticationConverter(
AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
return this;
}

public JwtConfigurer jwt() {
if (this.jwtConfigurer == null) {
this.jwtConfigurer = new JwtConfigurer(this.context);
Expand Down Expand Up @@ -252,8 +265,11 @@ public void configure(H http) {
AuthenticationManager authenticationManager = getAuthenticationManager(http);
resolver = (request) -> authenticationManager;
}

this.authenticationConverter = getBearerTokenAuthenticationConverter();

BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
filter.setBearerTokenResolver(bearerTokenResolver);
filter.setAuthenticationConverter(this.authenticationConverter);
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter = postProcess(filter);
http.addFilter(filter);
Expand Down Expand Up @@ -347,6 +363,20 @@ BearerTokenResolver getBearerTokenResolver() {
return this.bearerTokenResolver;
}

AuthenticationConverter getBearerTokenAuthenticationConverter() {
if (this.authenticationConverter == null) {
if (this.context.getBeanNamesForType(BearerTokenAuthenticationConverter.class).length > 0) {
this.authenticationConverter = this.context.getBean(BearerTokenAuthenticationConverter.class);
}
else {
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
converter.setBearerTokenResolver(getBearerTokenResolver());
this.authenticationConverter = converter;
}
}
return this.authenticationConverter;
}

public class JwtConfigurer {

private final ApplicationContext context;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@
import java.util.stream.Collectors;

import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletRequest;

import com.nimbusds.jose.JWSAlgorithm;
import com.nimbusds.jose.JWSHeader;
Expand Down Expand Up @@ -108,7 +109,9 @@
import org.springframework.security.oauth2.jwt.JwtTimestampValidator;
import org.springframework.security.oauth2.jwt.NimbusJwtDecoder;
import org.springframework.security.oauth2.jwt.TestJwts;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthentication;
import org.springframework.security.oauth2.server.resource.authentication.BearerTokenAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationConverter;
import org.springframework.security.oauth2.server.resource.authentication.JwtAuthenticationToken;
import org.springframework.security.oauth2.server.resource.authentication.JwtIssuerAuthenticationManagerResolver;
Expand Down Expand Up @@ -720,6 +723,72 @@ public void getBearerTokenResolverWhenNoResolverSpecifiedThenTheDefaultIsUsed()
assertThat(oauth2.getBearerTokenResolver()).isInstanceOf(DefaultBearerTokenResolver.class);
}

@Test
public void getBearerTokenAuthenticationConverterWhenDuplicateConverterBeansAndAnotherOnTheDslThenTheDslOneIsUsed() {
BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean("converterOne", BearerTokenAuthenticationConverter.class, () -> converterBean);
context.registerBean("converterTwo", BearerTokenAuthenticationConverter.class, () -> converterBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.bearerTokenAuthenticationConverter(converter);
assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converter);
}

@Test
public void getBearerTokenAuthenticationConverterWhenDuplicateConverterBeansThenWiringException() {
assertThatExceptionOfType(BeanCreationException.class).isThrownBy(() -> this.spring
.register(MultipleBearerTokenAuthenticationConverterBeansConfig.class, JwtDecoderConfig.class)
.autowire()).withRootCauseInstanceOf(NoUniqueBeanDefinitionException.class);
}

@Test
public void getBearerTokenAuthenticationConverterWhenConverterBeanAndAnotherOnTheDslThenTheDslOneIsUsed() {
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean(BearerTokenAuthenticationConverter.class, () -> converterBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
oauth2.bearerTokenAuthenticationConverter(converter);
assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converter);
}

@Test
public void getBearerTokenAuthenticationConverterWhenNoConverterSpecifiedThenTheDefaultIsUsed() {
ApplicationContext context = this.spring.context(new GenericWebApplicationContext()).getContext();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
assertThat(oauth2.getBearerTokenAuthenticationConverter())
.isInstanceOf(BearerTokenAuthenticationConverter.class);
}

@Test
public void getBearerTokenAuthenticationConverterWhenConverterBeanRegisteredThenBeanIsUsed() {
BearerTokenAuthenticationConverter converterBean = new BearerTokenAuthenticationConverter();
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean(BearerTokenAuthenticationConverter.class, () -> converterBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
assertThat(oauth2.getBearerTokenAuthenticationConverter()).isEqualTo(converterBean);

}

@Test
public void getBearerTokenAuthenticationConverterWhenOnlyResolverBeanRegisteredThenUseTheResolver() {
HttpServletRequest servletRequest = mock(HttpServletRequest.class);
BearerTokenResolver resolverBean = (request) -> "bearer customToken";
GenericWebApplicationContext context = new GenericWebApplicationContext();
context.registerBean(BearerTokenResolver.class, () -> resolverBean);
this.spring.context(context).autowire();
OAuth2ResourceServerConfigurer oauth2 = new OAuth2ResourceServerConfigurer(context);
BearerTokenAuthenticationToken bearerTokenAuthenticationToken = (BearerTokenAuthenticationToken) oauth2
.getBearerTokenAuthenticationConverter().convert(servletRequest);
String token = bearerTokenAuthenticationToken.getToken();
assertThat(token).isEqualTo("bearer customToken");

}

@Test
public void requestWhenCustomJwtDecoderWiredOnDslThenUsed() throws Exception {
this.spring.register(CustomJwtDecoderOnDsl.class, BasicController.class).autowire();
Expand Down Expand Up @@ -1871,6 +1940,32 @@ BearerTokenResolver resolverTwo() {

}

@EnableWebSecurity
static class MultipleBearerTokenAuthenticationConverterBeansConfig extends WebSecurityConfigurerAdapter {

@Override
protected void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.oauth2ResourceServer()
.jwt();
// @formatter:on
}

@Bean
BearerTokenAuthenticationConverter converterOne() {
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
return converter;
}

@Bean
BearerTokenAuthenticationConverter converterTwo() {
BearerTokenAuthenticationConverter converter = new BearerTokenAuthenticationConverter();
return converter;
}

}

@EnableWebSecurity
static class CustomJwtDecoderOnDsl extends WebSecurityConfigurerAdapter {

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
/*
* Copyright 2002-2021 the original author or authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package org.springframework.security.oauth2.server.resource.authentication;

import javax.servlet.http.HttpServletRequest;

import org.springframework.security.authentication.AuthenticationDetailsSource;
import org.springframework.security.oauth2.server.resource.BearerTokenAuthenticationToken;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.authentication.WebAuthenticationDetailsSource;
import org.springframework.util.Assert;

/**
* Converts from a HttpServletRequest to {@link BearerTokenAuthenticationToken} that can
* be authenticated. Null authentication possible if there was no Authorization header
* with Bearer Token.
*
* @author Jeongjin Kim
* @since 5.5
*/
public final class BearerTokenAuthenticationConverter implements AuthenticationConverter {

private AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource = new WebAuthenticationDetailsSource();

private BearerTokenResolver bearerTokenResolver;

public BearerTokenAuthenticationConverter() {
this.bearerTokenResolver = new DefaultBearerTokenResolver();
}

@Override
public BearerTokenAuthenticationToken convert(HttpServletRequest request) {
String token = this.bearerTokenResolver.resolve(request);

if (token == null) {
return null;
}

BearerTokenAuthenticationToken authenticationRequest = new BearerTokenAuthenticationToken(token);
authenticationRequest.setDetails(this.authenticationDetailsSource.buildDetails(request));
return authenticationRequest;
}

/**
* Set the {@link BearerTokenResolver} to use. Defaults to
* {@link DefaultBearerTokenResolver}.
* @param bearerTokenResolver the {@code BearerTokenResolver} to use
*/
public void setBearerTokenResolver(BearerTokenResolver bearerTokenResolver) {
Assert.notNull(bearerTokenResolver, "bearerTokenResolver cannot be null");
this.bearerTokenResolver = bearerTokenResolver;
}

/**
* Set the {@link AuthenticationDetailsSource} to use. Defaults to
* {@link WebAuthenticationDetailsSource}.
* @param authenticationDetailsSource the {@code AuthenticationDetailsSource} to use
*/
public void setAuthenticationDetailsSource(
AuthenticationDetailsSource<HttpServletRequest, ?> authenticationDetailsSource) {
Assert.notNull(authenticationDetailsSource, "authenticationDetailsSource cannot be null");
this.authenticationDetailsSource = authenticationDetailsSource;
}

}
Loading