Skip to content

Commit

Permalink
Add support BearerTokenAuthenticationConverter
Browse files Browse the repository at this point in the history
Closes gh-14750
  • Loading branch information
Max Batischev authored and Max Batischev committed Apr 8, 2024
1 parent c8e5fbf commit 3317b0d
Show file tree
Hide file tree
Showing 8 changed files with 758 additions and 53 deletions.
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2023 the original author or authors.
* Copyright 2002-2024 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 @@ -41,6 +41,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 @@ -49,13 +50,13 @@
import org.springframework.security.oauth2.server.resource.introspection.SpringOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.AuthenticationEntryPoint;
import org.springframework.security.web.access.AccessDeniedHandler;
import org.springframework.security.web.access.AccessDeniedHandlerImpl;
import org.springframework.security.web.access.DelegatingAccessDeniedHandler;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.csrf.CsrfException;
import org.springframework.security.web.util.matcher.AndRequestMatcher;
import org.springframework.security.web.util.matcher.MediaTypeRequestMatcher;
Expand All @@ -68,9 +69,8 @@
import org.springframework.web.accept.HeaderContentNegotiationStrategy;

/**
*
* An {@link AbstractHttpConfigurer} for OAuth 2.0 Resource Server Support.
*
* <p>
* By default, this wires a {@link BearerTokenAuthenticationFilter}, which can be used to
* parse the request for bearer tokens and make an authentication attempt.
*
Expand All @@ -84,6 +84,8 @@
* authentication failures are handled
* <li>{@link #bearerTokenResolver(BearerTokenResolver)} - customizes how to resolve a
* bearer token from the request</li>
* <li>{@link #authenticationConverter(AuthenticationConverter)} - customizes how to
* convert a request to authentication</li>
* <li>{@link #jwt(Customizer)} - enables Jwt-encoded bearer token support</li>
* <li>{@link #opaqueToken(Customizer)} - enables opaque bearer token support</li>
* </ul>
Expand All @@ -96,7 +98,7 @@
* <li>supply a {@link JwtDecoder} instance via {@link JwtConfigurer#decoder}, or</li>
* <li>expose a {@link JwtDecoder} bean</li>
* </ul>
*
* <p>
* Also with {@link #jwt(Customizer)} consider
*
* <ul>
Expand All @@ -111,7 +113,7 @@
* </p>
*
* <h2>Security Filters</h2>
*
* <p>
* The following {@code Filter}s are populated when {@link #jwt(Customizer)} is
* configured:
*
Expand All @@ -120,15 +122,15 @@
* </ul>
*
* <h2>Shared Objects Created</h2>
*
* <p>
* The following shared objects are populated:
*
* <ul>
* <li>{@link SessionCreationPolicy} (optional)</li>
* </ul>
*
* <h2>Shared Objects Used</h2>
*
* <p>
* The following shared objects are used:
*
* <ul>
Expand Down Expand Up @@ -156,6 +158,8 @@ public final class OAuth2ResourceServerConfigurer<H extends HttpSecurityBuilder<

private BearerTokenResolver bearerTokenResolver;

private AuthenticationConverter authenticationConverter;

private JwtConfigurer jwtConfigurer;

private OpaqueTokenConfigurer opaqueTokenConfigurer;
Expand Down Expand Up @@ -198,6 +202,12 @@ public OAuth2ResourceServerConfigurer<H> bearerTokenResolver(BearerTokenResolver
return this;
}

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

/**
* @deprecated For removal in 7.0. Use {@link #jwt(Customizer)} or
* {@code jwt(Customizer.withDefaults())} to stick with defaults. See the <a href=
Expand Down Expand Up @@ -269,16 +279,25 @@ public void init(H http) {

@Override
public void configure(H http) {
BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
AuthenticationManagerResolver resolver = this.authenticationManagerResolver;
if (resolver == null) {
AuthenticationManager authenticationManager = getAuthenticationManager(http);
resolver = (request) -> authenticationManager;
}

BearerTokenAuthenticationFilter filter = new BearerTokenAuthenticationFilter(resolver);
filter.setBearerTokenResolver(bearerTokenResolver);

BearerTokenResolver bearerTokenResolver = getBearerTokenResolver();
if (bearerTokenResolver != null) {
this.requestMatcher.setBearerTokenResolver(bearerTokenResolver);
filter.setBearerTokenResolver(bearerTokenResolver);
}
else {
AuthenticationConverter converter = getAuthenticationConverter();
this.requestMatcher.setAuthenticationConverter(converter);
filter.setAuthenticationConverter(converter);
}

filter.setAuthenticationConverter(getAuthenticationConverter());
filter.setAuthenticationEntryPoint(this.authenticationEntryPoint);
filter.setSecurityContextHolderStrategy(getSecurityContextHolderStrategy());
filter = postProcess(filter);
Expand Down Expand Up @@ -366,11 +385,20 @@ BearerTokenResolver getBearerTokenResolver() {
if (this.context.getBeanNamesForType(BearerTokenResolver.class).length > 0) {
this.bearerTokenResolver = this.context.getBean(BearerTokenResolver.class);
}
}
return this.bearerTokenResolver;
}

AuthenticationConverter getAuthenticationConverter() {
if (this.authenticationConverter == null) {
if (this.context.getBeanNamesForType(AuthenticationConverter.class).length > 0) {
this.authenticationConverter = this.context.getBean(AuthenticationConverter.class);
}
else {
this.bearerTokenResolver = new DefaultBearerTokenResolver();
this.authenticationConverter = new BearerTokenAuthenticationConverter();
}
}
return this.bearerTokenResolver;
return this.authenticationConverter;
}

public class JwtConfigurer {
Expand Down Expand Up @@ -560,10 +588,15 @@ private static final class BearerTokenRequestMatcher implements RequestMatcher {

private BearerTokenResolver bearerTokenResolver;

private AuthenticationConverter authenticationConverter;

@Override
public boolean matches(HttpServletRequest request) {
try {
return this.bearerTokenResolver.resolve(request) != null;
if (this.bearerTokenResolver != null) {
return this.bearerTokenResolver.resolve(request) != null;
}
return this.authenticationConverter.convert(request) != null;
}
catch (OAuth2AuthenticationException ex) {
return false;
Expand All @@ -575,6 +608,11 @@ void setBearerTokenResolver(BearerTokenResolver tokenResolver) {
this.bearerTokenResolver = tokenResolver;
}

void setAuthenticationConverter(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}

}

}
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
/*
* Copyright 2002-2022 the original author or authors.
* Copyright 2002-2024 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 @@ -37,15 +37,16 @@
import org.springframework.security.oauth2.core.OAuth2AuthenticationException;
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;
import org.springframework.security.oauth2.server.resource.introspection.NimbusOpaqueTokenIntrospector;
import org.springframework.security.oauth2.server.resource.web.BearerTokenAuthenticationEntryPoint;
import org.springframework.security.oauth2.server.resource.web.BearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.DefaultBearerTokenResolver;
import org.springframework.security.oauth2.server.resource.web.access.BearerTokenAccessDeniedHandler;
import org.springframework.security.oauth2.server.resource.web.authentication.BearerTokenAuthenticationFilter;
import org.springframework.security.web.authentication.AuthenticationConverter;
import org.springframework.security.web.util.matcher.RequestMatcher;
import org.springframework.util.Assert;
import org.springframework.util.StringUtils;
Expand All @@ -64,10 +65,14 @@ final class OAuth2ResourceServerBeanDefinitionParser implements BeanDefinitionPa

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

static final String AUTHENTICATION_CONVERTER_REF = "authentication-converter-ref";

static final String ENTRY_POINT_REF = "entry-point-ref";

static final String BEARER_TOKEN_RESOLVER = "bearerTokenResolver";

static final String AUTHENTICATION_CONVERTER = "authenticationConverter";

static final String AUTHENTICATION_ENTRY_POINT = "authenticationEntryPoint";

private final BeanReference authenticationManager;
Expand Down Expand Up @@ -124,25 +129,40 @@ public BeanDefinition parse(Element oauth2ResourceServer, ParserContext pc) {
pc.getReaderContext().registerWithGeneratedName(opaqueTokenAuthenticationProvider)));
}
BeanMetadataElement bearerTokenResolver = getBearerTokenResolver(oauth2ResourceServer);
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
BeanDefinition requestMatcher = requestMatcherBuilder.getBeanDefinition();
BeanMetadataElement authenticationConverter = getAuthenticationConverter(oauth2ResourceServer);
BeanMetadataElement authenticationEntryPoint = getEntryPoint(oauth2ResourceServer);
BeanDefinition requestMatcher = buildRequestMatcher(bearerTokenResolver, authenticationConverter);
this.entryPoints.put(requestMatcher, authenticationEntryPoint);
this.deniedHandlers.put(requestMatcher, this.accessDeniedHandler);
this.ignoreCsrfRequestMatchers.add(requestMatcher);
BeanDefinitionBuilder filterBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenAuthenticationFilter.class);
BeanMetadataElement authenticationManagerResolver = getAuthenticationManagerResolver(oauth2ResourceServer);
filterBuilder.addConstructorArgValue(authenticationManagerResolver);
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
filterBuilder.addPropertyValue(AUTHENTICATION_CONVERTER, authenticationConverter);
filterBuilder.addPropertyValue(AUTHENTICATION_ENTRY_POINT, authenticationEntryPoint);
filterBuilder.addPropertyValue("securityContextHolderStrategy",
this.authenticationFilterSecurityContextHolderStrategy);
if (bearerTokenResolver != null) {
filterBuilder.addPropertyValue(BEARER_TOKEN_RESOLVER, bearerTokenResolver);
}
return filterBuilder.getBeanDefinition();
}

private BeanDefinition buildRequestMatcher(BeanMetadataElement bearerTokenResolver,
BeanMetadataElement authenticationConverter) {
if (bearerTokenResolver != null) {
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(bearerTokenResolver);
return requestMatcherBuilder.getBeanDefinition();
}
BeanDefinitionBuilder requestMatcherBuilder = BeanDefinitionBuilder
.rootBeanDefinition(BearerTokenAuthenticationRequestMatcher.class);
requestMatcherBuilder.addConstructorArgValue(authenticationConverter);
return requestMatcherBuilder.getBeanDefinition();
}

void validateConfiguration(Element oauth2ResourceServer, Element jwt, Element opaqueToken, ParserContext pc) {
if (!oauth2ResourceServer.hasAttribute(AUTHENTICATION_MANAGER_RESOLVER_REF)) {
if (jwt == null && opaqueToken == null) {
Expand Down Expand Up @@ -178,11 +198,19 @@ BeanMetadataElement getAuthenticationManagerResolver(Element element) {
BeanMetadataElement getBearerTokenResolver(Element element) {
String bearerTokenResolverRef = element.getAttribute(BEARER_TOKEN_RESOLVER_REF);
if (!StringUtils.hasLength(bearerTokenResolverRef)) {
return new RootBeanDefinition(DefaultBearerTokenResolver.class);
return null;
}
return new RuntimeBeanReference(bearerTokenResolverRef);
}

BeanMetadataElement getAuthenticationConverter(Element element) {
String authenticationConverterRef = element.getAttribute(AUTHENTICATION_CONVERTER_REF);
if (!StringUtils.hasLength(authenticationConverterRef)) {
return new RootBeanDefinition(BearerTokenAuthenticationConverter.class);
}
return new RuntimeBeanReference(authenticationConverterRef);
}

BeanMetadataElement getEntryPoint(Element element) {
String entryPointRef = element.getAttribute(ENTRY_POINT_REF);
if (!StringUtils.hasLength(entryPointRef)) {
Expand Down Expand Up @@ -366,4 +394,25 @@ public boolean matches(HttpServletRequest request) {

}

static final class BearerTokenAuthenticationRequestMatcher implements RequestMatcher {

private final AuthenticationConverter authenticationConverter;

BearerTokenAuthenticationRequestMatcher(AuthenticationConverter authenticationConverter) {
Assert.notNull(authenticationConverter, "authenticationConverter cannot be null");
this.authenticationConverter = authenticationConverter;
}

@Override
public boolean matches(HttpServletRequest request) {
try {
return this.authenticationConverter.convert(request) != null;
}
catch (OAuth2AuthenticationException ex) {
return false;
}
}

}

}
Loading

0 comments on commit 3317b0d

Please sign in to comment.