Skip to content

ServletOAuth2AuthorizedClientExchangeFilterFunction sends access token (not JWT) instead of id token for Google OAuth2 #8976

@rdavudov

Description

@rdavudov

Describe the bug
Google Access Token (Not JWT) is sent as a Bearer header while calling a resource server using Web Client. At resource server a valid JWT is expected but access token can not be parsed as JWT and 401 is returned.

http://localhost:8080/google endpoint calls http://localhost:8081/secured-google

When we call http://localhost:8080/google endpoint we get authenticated by google and receive 500 http error. In logs we can see

org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized: 401 Unauthorized from GET http://localhost:8081/secured-google
	at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:181) ~[spring-webflux-5.2.8.RELEASE.jar:5.2.8.RELEASE]
	Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException: 
Error has been observed at the following site(s):
	|_ checkpoint ⇢ 401 from GET http://localhost:8081/secured-google [DefaultWebClient]
Stack trace:
		at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:181) ~[spring-webflux-5.2.8.RELEASE.jar:5.2.8.RELEASE]
		at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:206) ~[spring-webflux-5.2.8.RELEASE.jar:5.2.8.RELEASE]
		at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:100) ~[reactor-core-3.3.9.RELEASE.jar:3.3.9.RELEASE]
		at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1782) ~[reactor-core-3.3.9.RELEASE.jar:3.3.9.RELEASE]
		at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onComplete(FluxDefaultIfEmpty.java:100) ~[reactor-core-3.3.9.RELEASE.jar:3.3.9.RELEASE]
		at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onComplete(FluxMapFuseable.java:144) ~[reactor-core-3.3.9.RELEASE.jar:3.3.9.RELEASE]
...

Problem is in class ServletOAuth2AuthorizedClientExchangeFilterFunction where access token is added a Authorization header.

	private ClientRequest bearer(ClientRequest request, OAuth2AuthorizedClient authorizedClient) {
		return ClientRequest.from(request)
					.headers(headers -> headers.setBearerAuth(authorizedClient.getAccessToken().getTokenValue()))
					.attributes(oauth2AuthorizedClient(authorizedClient))
					.build();
	}

To Reproduce

Create a Spring Boot Client Application

  1. Create a client spring boot application with oauth2 login security configuration.
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests().anyRequest().authenticated()
			.and().oauth2Login() ;
	}
}
  1. Create a custom WebClient bean to use ServletOAuth2AuthorizedClientExchangeFilterFunction
@Configuration
public class WebClientConfig {
	@Bean
	protected WebClient webClient(ClientRegistrationRepository clientRegistrationRepository, OAuth2AuthorizedClientRepository auth2AuthorizedClientRepository) {
		
		ServletOAuth2AuthorizedClientExchangeFilterFunction oauthFilterFunction = 
				new ServletOAuth2AuthorizedClientExchangeFilterFunction(clientRegistrationRepository, auth2AuthorizedClientRepository) ;

		return WebClient.builder().apply(oauthFilterFunction.oauth2Configuration()).build();
	}
}
  1. Create a controller to use WebClient in order to call a resource server protected via JWT.
@RestController
public class GoogleController {
	@Autowired
	private WebClient webClient ;
	@GetMapping("/google")
	public String google(@RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient,
			@AuthenticationPrincipal OAuth2User oauth2User) {
		
		return this.webClient
				.get()
				.uri("http://localhost:8081/secured-google")
				.attributes(ServletOAuth2AuthorizedClientExchangeFilterFunction.oauth2AuthorizedClient(authorizedClient))
				.retrieve()
				.bodyToMono(String.class)
				.block() ;
	}
}
  1. Configure application.yml for using google oauth
server.port: 8080
spring:
  security:
    oauth2:
      client:
        registration:
          google:   
            client-id: <CLIENT-ID>
            client-secret: <CLIENT-SECRET>

Client a Spring Boot Resource Server

  1. Create another spring boot resource sever application with following security config
@Configuration
public class SecurityConfig extends WebSecurityConfigurerAdapter {
	@Override
	protected void configure(HttpSecurity http) throws Exception {
		http
			.authorizeRequests().anyRequest().authenticated()
			.and().oauth2ResourceServer().jwt();
	}
}
  1. Create a controller to be authenticated via JWT
@RestController
public class SecuredGoogleController {
	@GetMapping("/secured-google")
	public String secured(JwtAuthenticationToken token) {
		return "secured id " + token.getName() ;
	}
}
  1. Configure application.yml for using google oauth
server.port: 8081
spring:
  security:
    oauth2:
      resourceserver:
        jwt:
          issuer-uri: https://accounts.google.com
          jwk-set-uri: https://www.googleapis.com/oauth2/v3/certs

Expected behavior
OidcUser IdToken (which is JWT) must be sent instead of Access Token.

Github repository
You can find minimal and reproducible sample at github repo https://github.com/rdavudov/spring-security.

Workaround
Manually provide Id Token using web client as a header.

	@GetMapping("/google-ok")
	public String googleOk(@RegisteredOAuth2AuthorizedClient("google") OAuth2AuthorizedClient authorizedClient,
			@AuthenticationPrincipal OAuth2User oauth2User) {
		String idToken = ((OidcUser) oauth2User).getIdToken().getTokenValue() ;
		
		return this.webClient
				.get()
				.uri("http://localhost:8081/secured-google")
				.headers(header -> header.setBearerAuth(idToken))
				.retrieve()
				.bodyToMono(String.class)
				.block() ;
	}

Metadata

Metadata

Assignees

Labels

in: oauth2An issue in OAuth2 modules (oauth2-core, oauth2-client, oauth2-resource-server, oauth2-jose)status: invalidAn issue that we don't feel is valid

Type

No type
No fields configured for issues without a type.

Projects

No projects

Milestone

No milestone

Relationships

None yet

Development

No branches or pull requests

Issue actions