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
- 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() ;
}
}
- 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();
}
}
- 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() ;
}
}
- 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
- 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();
}
}
- 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() ;
}
}
- 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() ;
}
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/googleendpoint callshttp://localhost:8081/secured-googleWhen we call
http://localhost:8080/googleendpoint we get authenticated by google and receive 500 http error. In logs we can seeProblem is in class
ServletOAuth2AuthorizedClientExchangeFilterFunctionwhere access token is added a Authorization header.To Reproduce
Create a Spring Boot Client Application
ServletOAuth2AuthorizedClientExchangeFilterFunctionapplication.ymlfor using google oauthClient a Spring Boot Resource Server
application.ymlfor using google oauthExpected 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.