Consuming REST API with OAuth2 + Spring Boot

Viewed 669

I'm having trouble integrating with an external REST API that is secured with OAuth2 via Spring's oauth2 client. I need to retrieve an access token prior to making any API calls, I can do this with curl following the specs outlined here https://datatracker.ietf.org/doc/html/rfc6749#section-4.3.2.

Spring's OAuth2 client dependency:

implementation("org.springframework.boot:spring-boot-starter-oauth2-client")

Spring boot app's application.properties:

spring.security.oauth2.client.registration.example.client-id=foo-client-id
spring.security.oauth2.client.registration.example.client-secret=foo-client-secret
spring.security.oauth2.client.registration.example.authorization-grant-type=password
spring.security.oauth2.client.provider.example.token-uri=https://example.com/api/token

I tried best to follow Spring's documentation as read here -> https://docs.spring.io/spring-security/site/docs/current/reference/html5/#oauth2Client-webclient-servlet

Here is my configuration and the WebClient I'm attempting to use for the external REST API:

@Configuration
class ExampleApiConfig {
    @Bean
    fun authorizedClientManager(
            clientRegistrationRepository: ClientRegistrationRepository,
            authorizedClientRepository: OAuth2AuthorizedClientRepository): OAuth2AuthorizedClientManager {
        val authorizedClientProvider = OAuth2AuthorizedClientProviderBuilder.builder()
                .password()
                .refreshToken()
                .build()
        val authorizedClientManager = DefaultOAuth2AuthorizedClientManager(
                clientRegistrationRepository, authorizedClientRepository)
        authorizedClientManager.setAuthorizedClientProvider(authorizedClientProvider)


        return authorizedClientManager
    }

    @Bean("exampleWebClient")
    fun exampleWebClient(authorizedClientManager: OAuth2AuthorizedClientManager): WebClient {
        val oauth2Client = ServletOAuth2AuthorizedClientExchangeFilterFunction(authorizedClientManager)
        oauth2Client.setDefaultClientRegistrationId("example")
        return WebClient.builder()
                .baseUrl("https://example.com/api")
                .apply(oauth2Client.oauth2Configuration())
                .build()
    }

   
}

Here is the attempted use of the Example WebClient

@Service
class ExampleApiClient(private val exampleWebClient: WebClient) {

    fun test(): String? {
        return exampleWebClient
                .get()
                .uri {
                    it.path("/profile/getbyid")
                            .queryParam("fooid", "some-foo-id")
                            .build()
                }
                .attributes { clientRegistrationId("example") }
                .retrieve()
                .bodyToMono(String::class.java)
                .block()
    }
}

I get a 401 as seen below. I'm not sure if the token is never being retrieved, but it clearly is not being included in the request.

16:41:54.667 [http-nio-8080-exec-2] ERROR c.r.api.error.ExceptionHandler - 401 Unauthorized from GET https://example.com/api/profile/getbyid?fooid=some-foo-id
org.springframework.web.reactive.function.client.WebClientResponseException$Unauthorized: 401 Unauthorized from GET https://example.com/api/profile/getbyid?fooid=some-foo-id
        at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:198)
        Suppressed: reactor.core.publisher.FluxOnAssembly$OnAssemblyException:
Error has been observed at the following site(s):
        |_ checkpoint ? 401 from GET https://example.com/api/profile/getbyid?fooid=some-foo-id [DefaultWebClient]
Stack trace:
                at org.springframework.web.reactive.function.client.WebClientResponseException.create(WebClientResponseException.java:198)
                at org.springframework.web.reactive.function.client.DefaultClientResponse.lambda$createException$1(DefaultClientResponse.java:213)
                at reactor.core.publisher.FluxMap$MapSubscriber.onNext(FluxMap.java:106)
                at reactor.core.publisher.FluxOnErrorResume$ResumeSubscriber.onNext(FluxOnErrorResume.java:79)
                at reactor.core.publisher.FluxDefaultIfEmpty$DefaultIfEmptySubscriber.onNext(FluxDefaultIfEmpty.java:101)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableSubscriber.onNext(FluxMapFuseable.java:127)
                at reactor.core.publisher.FluxContextWrite$ContextWriteSubscriber.onNext(FluxContextWrite.java:107)
                at reactor.core.publisher.FluxMapFuseable$MapFuseableConditionalSubscriber.onNext(FluxMapFuseable.java:295)
                at reactor.core.publisher.FluxFilterFuseable$FilterFuseableConditionalSubscriber.onNext(FluxFilterFuseable.java:337)
                at reactor.core.publisher.Operators$MonoSubscriber.complete(Operators.java:1816)
                at reactor.core.publisher.MonoCollect$CollectSubscriber.onComplete(MonoCollect.java:159)
                at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
                at reactor.core.publisher.FluxPeek$PeekSubscriber.onComplete(FluxPeek.java:260)
                at reactor.core.publisher.FluxMap$MapSubscriber.onComplete(FluxMap.java:142)
                at reactor.netty.channel.FluxReceive.onInboundComplete(FluxReceive.java:400)
                at reactor.netty.channel.ChannelOperations.onInboundComplete(ChannelOperations.java:419)
                at reactor.netty.channel.ChannelOperations.terminate(ChannelOperations.java:473)
                at reactor.netty.http.client.HttpClientOperations.onInboundNext(HttpClientOperations.java:684)
                at reactor.netty.channel.ChannelOperationsHandler.channelRead(ChannelOperationsHandler.java:93)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
                at io.netty.handler.codec.MessageToMessageDecoder.channelRead(MessageToMessageDecoder.java:103)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
                at io.netty.channel.CombinedChannelDuplexHandler$DelegatingChannelHandlerContext.fireChannelRead(CombinedChannelDuplexHandler.java:436)
                at io.netty.handler.codec.ByteToMessageDecoder.fireChannelRead(ByteToMessageDecoder.java:324)
                at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:296)
                at io.netty.channel.CombinedChannelDuplexHandler.channelRead(CombinedChannelDuplexHandler.java:251)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
                at io.netty.handler.ssl.SslHandler.unwrap(SslHandler.java:1373)
                at io.netty.handler.ssl.SslHandler.decodeJdkCompatible(SslHandler.java:1236)
                at io.netty.handler.ssl.SslHandler.decode(SslHandler.java:1285)
                at io.netty.handler.codec.ByteToMessageDecoder.decodeRemovalReentryProtection(ByteToMessageDecoder.java:507)
                at io.netty.handler.codec.ByteToMessageDecoder.callDecode(ByteToMessageDecoder.java:446)
                at io.netty.handler.codec.ByteToMessageDecoder.channelRead(ByteToMessageDecoder.java:276)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
                at io.netty.channel.AbstractChannelHandlerContext.fireChannelRead(AbstractChannelHandlerContext.java:357)
                at io.netty.channel.DefaultChannelPipeline$HeadContext.channelRead(DefaultChannelPipeline.java:1410)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:379)
                at io.netty.channel.AbstractChannelHandlerContext.invokeChannelRead(AbstractChannelHandlerContext.java:365)
                at io.netty.channel.DefaultChannelPipeline.fireChannelRead(DefaultChannelPipeline.java:919)
                at io.netty.channel.nio.AbstractNioByteChannel$NioByteUnsafe.read(AbstractNioByteChannel.java:166)
                at io.netty.channel.nio.NioEventLoop.processSelectedKey(NioEventLoop.java:719)
                at io.netty.channel.nio.NioEventLoop.processSelectedKeysOptimized(NioEventLoop.java:655)
                at io.netty.channel.nio.NioEventLoop.processSelectedKeys(NioEventLoop.java:581)
                at io.netty.channel.nio.NioEventLoop.run(NioEventLoop.java:493)
                at io.netty.util.concurrent.SingleThreadEventExecutor$4.run(SingleThreadEventExecutor.java:986)
                at io.netty.util.internal.ThreadExecutorMap$2.run(ThreadExecutorMap.java:74)
                at io.netty.util.concurrent.FastThreadLocalRunnable.run(FastThreadLocalRunnable.java:30)
                at java.base/java.lang.Thread.run(Thread.java:834)
        Suppressed: java.lang.Exception: #block terminated with an error
                at reactor.core.publisher.BlockingSingleSubscriber.blockingGet(BlockingSingleSubscriber.java:99)
                at reactor.core.publisher.Mono.block(Mono.java:1704)
                at com.roofmarketplace.service.profilegorilla.ProfileGorillaApiClient.test(ProfileGorillaApiClient.kt:23)
                at com.roofmarketplace.api.profile-gorilla.ProfileGorillaController.test(ProfileGorillaController.kt:14)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
                at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
                at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
                at java.base/java.lang.reflect.Method.invoke(Method.java:566)
                at org.springframework.web.method.support.InvocableHandlerMethod.doInvoke(InvocableHandlerMethod.java:197)
                at org.springframework.web.method.support.InvocableHandlerMethod.invokeForRequest(InvocableHandlerMethod.java:141)
                at org.springframework.web.servlet.mvc.method.annotation.ServletInvocableHandlerMethod.invokeAndHandle(ServletInvocableHandlerMethod.java:106)
                at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.invokeHandlerMethod(RequestMappingHandlerAdapter.java:895)
                at org.springframework.web.servlet.mvc.method.annotation.RequestMappingHandlerAdapter.handleInternal(RequestMappingHandlerAdapter.java:808)
                at org.springframework.web.servlet.mvc.method.AbstractHandlerMethodAdapter.handle(AbstractHandlerMethodAdapter.java:87)
                at org.springframework.web.servlet.DispatcherServlet.doDispatch(DispatcherServlet.java:1064)
                at org.springframework.web.servlet.DispatcherServlet.doService(DispatcherServlet.java:963)
                at org.springframework.web.servlet.FrameworkServlet.processRequest(FrameworkServlet.java:1006)
                at org.springframework.web.servlet.FrameworkServlet.doGet(FrameworkServlet.java:898)
                at javax.servlet.http.HttpServlet.service(HttpServlet.java:655)
                at org.springframework.web.servlet.FrameworkServlet.service(FrameworkServlet.java:883)
                at javax.servlet.http.HttpServlet.service(HttpServlet.java:764)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:227)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.apache.tomcat.websocket.server.WsFilter.doFilter(WsFilter.java:53)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:327)
                at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.invoke(FilterSecurityInterceptor.java:115)
                at org.springframework.security.web.access.intercept.FilterSecurityInterceptor.doFilter(FilterSecurityInterceptor.java:81)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:121)
                at org.springframework.security.web.access.ExceptionTranslationFilter.doFilter(ExceptionTranslationFilter.java:115)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:126)
                at org.springframework.security.web.session.SessionManagementFilter.doFilter(SessionManagementFilter.java:81)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.authentication.AnonymousAuthenticationFilter.doFilter(AnonymousAuthenticationFilter.java:105)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.servletapi.SecurityContextHolderAwareRequestFilter.doFilter(SecurityContextHolderAwareRequestFilter.java:149)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.savedrequest.RequestCacheAwareFilter.doFilter(RequestCacheAwareFilter.java:63)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at com.roofmarketplace.api.security.JwtAuthenticationFilter.doFilterInternal(JwtAuthenticationFilter.kt:31)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:218)
                at org.springframework.security.web.authentication.AbstractAuthenticationProcessingFilter.doFilter(AbstractAuthenticationProcessingFilter.java:212)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:103)
                at org.springframework.security.web.authentication.logout.LogoutFilter.doFilter(LogoutFilter.java:89)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.header.HeaderWriterFilter.doHeadersAfter(HeaderWriterFilter.java:90)
                at org.springframework.security.web.header.HeaderWriterFilter.doFilterInternal(HeaderWriterFilter.java:75)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:110)
                at org.springframework.security.web.context.SecurityContextPersistenceFilter.doFilter(SecurityContextPersistenceFilter.java:80)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.context.request.async.WebAsyncManagerIntegrationFilter.doFilterInternal(WebAsyncManagerIntegrationFilter.java:55)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
                at org.springframework.security.web.FilterChainProxy$VirtualFilterChain.doFilter(FilterChainProxy.java:336)
                at org.springframework.security.web.FilterChainProxy.doFilterInternal(FilterChainProxy.java:211)
                at org.springframework.security.web.FilterChainProxy.doFilter(FilterChainProxy.java:183)
                at org.springframework.web.filter.DelegatingFilterProxy.invokeDelegate(DelegatingFilterProxy.java:358)
                at org.springframework.web.filter.DelegatingFilterProxy.doFilter(DelegatingFilterProxy.java:271)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.springframework.web.filter.RequestContextFilter.doFilterInternal(RequestContextFilter.java:100)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.springframework.web.filter.FormContentFilter.doFilterInternal(FormContentFilter.java:93)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.springframework.web.filter.CharacterEncodingFilter.doFilterInternal(CharacterEncodingFilter.java:201)
                at org.springframework.web.filter.OncePerRequestFilter.doFilter(OncePerRequestFilter.java:119)
                at org.apache.catalina.core.ApplicationFilterChain.internalDoFilter(ApplicationFilterChain.java:189)
                at org.apache.catalina.core.ApplicationFilterChain.doFilter(ApplicationFilterChain.java:162)
                at org.apache.catalina.core.StandardWrapperValve.invoke(StandardWrapperValve.java:197)
                at org.apache.catalina.core.StandardContextValve.invoke(StandardContextValve.java:97)
                at org.apache.catalina.authenticator.AuthenticatorBase.invoke(AuthenticatorBase.java:542)
                at org.apache.catalina.core.StandardHostValve.invoke(StandardHostValve.java:135)
                at org.apache.catalina.valves.ErrorReportValve.invoke(ErrorReportValve.java:92)
                at org.apache.catalina.core.StandardEngineValve.invoke(StandardEngineValve.java:78)
                at org.apache.catalina.connector.CoyoteAdapter.service(CoyoteAdapter.java:357)
                at org.apache.coyote.http11.Http11Processor.service(Http11Processor.java:382)
                at org.apache.coyote.AbstractProcessorLight.process(AbstractProcessorLight.java:65)
                at org.apache.coyote.AbstractProtocol$ConnectionHandler.process(AbstractProtocol.java:893)
                at org.apache.tomcat.util.net.NioEndpoint$SocketProcessor.doRun(NioEndpoint.java:1726)
                at org.apache.tomcat.util.net.SocketProcessorBase.run(SocketProcessorBase.java:49)
                at org.apache.tomcat.util.threads.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1191)
                at org.apache.tomcat.util.threads.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:659)
                at org.apache.tomcat.util.threads.TaskThread$WrappingRunnable.run(TaskThread.java:61)
                at java.base/java.lang.Thread.run(Thread.java:834)

Any ideas where I might be going wrong in my setup? Or ways I could troubleshoot?

0 Answers
Related