Home > Blockchain >  Spring GraphQL with WebMvc getting request headers
Spring GraphQL with WebMvc getting request headers

Time:11-08

I have a Spring GraphQL project. Each data fetcher (@SchemaMapping) will get data from a remote API protected by authentication.

I need to propagate the authorization header from the original request (that I can see inside the @QueryMapping method) to the data fetcher.

In the data fetcher I can use RequestContextHolder to get the request and the headers like this:

    val request = (RequestContextHolder.getRequestAttributes() as ServletRequestAttributes?)?.getRequest()
    val token = request?.getHeader("authorization")

This works but I am worried it could break. Spring GraphQL documentation states that:

A DataFetcher and other components invoked by GraphQL Java may not always execute on the same thread as the Spring MVC handler, for example if an asynchronous WebInterceptor or DataFetcher switches to a different thread.

I tried adding a ThreadLocalAccessor component but it seems to me from debugging and reading source code that the restoreValue method gets called only in a WebFlux project.

How can I be sure to get the right RequestContextHolder in a WebMvc project?

UPDATE

I will add some code to better explain my use case.

CurrentActivity is the parent entity while Booking is the child entity.

I need to fetch the entities from a backend with APIs protected by authentication. I receive the auth token in the original request (the one with the graphql query).

CurrentActivityController.kt

@Controller
class CurrentActivityController @Autowired constructor(
    val retrofitApiService: RetrofitApiService,
    val request: HttpServletRequest
) {

    @QueryMapping
    fun currentActivity(graphQLContext: GraphQLContext): CurrentActivity {
        // Get auth token from request.
        // Can I use the injected request here?
        // Or do I need to use Filter   ThreadLocalAccessor to get the token?
        val token = request.getHeader("authorization")
        // Can I save the token to GraphQL Context?
        graphQLContext.put("AUTH_TOKEN", token) 
        return runBlocking {
        // Authenticated API call to backend to get the CurrentActivity
            return@runBlocking entityretrofitApiService.apiHandler.activitiesCurrent(mapOf("authorization" to token))
        }
    }
}

BookingController.kt

@Controller
class BookingController @Autowired constructor(val retrofitApiService: RetrofitApiService) {

    @SchemaMapping
    fun booking(
        currentActivity: CurrentActivity,
        graphQLContext: GraphQLContext,
    ): Booking? {
        // Can I retrieve the token from GraphQL context?
        val token: String = graphQLContext.get("AUTH_TOKEN")
        return runBlocking {
            // Authenticated API call to backend to get Booking entity
            return@runBlocking currentActivity.currentCarBookingId?.let { currentCarBookingId ->
                retrofitApiService.apiHandler.booking(
                    headerMap = mapOf("authorization" to token),
                    bookingId = currentCarBookingId
                )
            }
        }
    }
}

CodePudding user response:

The ThreadLocalAccessor concept is really meant as a way to store/restore context values in an environment where execution can happen asynchronously, on a different thread if no other infrastructure already supports that.

In the case of Spring WebFlux, the Reactor context is already present and fills this role. A WebFlux application should use reactive DataFetchers and the Reactor Context natively.

ThreadLocalAccessor implementations are mostly useful for Spring MVC apps. Any ThreadLocalAccessor bean will be auto-configured by the starter.

In your case, you could follow one of the samples and have a similar arrangement:

I tried adding a ThreadLocalAccessor component but it seems to me from debugging and reading source code that the restoreValue method gets called only in a WebFlux project.

Note that the restoreValue is only called if the current Thread is not the one values where extracted from originally (nothing needs to be done, values are already in the ThreadLocal).

I've successfully tested this approach, getting the "authorization" HTTP header value from the RequestContextHolder. It seems you tried this approach unsuccessfully - could you try with 1.0.0-M3 and let us know if it doesn't work? You can create an issue on the project with a link to a sample project that reproduces the issue.

Alternate solution

If you don't want to deal with ThreadLocal-bound values, you can always use a WebInterceptor to augment the GraphQLContext with custom values.

Here's an example:

@Component
public class AuthorizationWebInterceptor implements WebInterceptor {
    @Override
    public Mono<WebOutput> intercept(WebInput webInput, WebInterceptorChain chain) {
        String authorization = webInput.getHeaders().getFirst(HttpHeaders.AUTHORIZATION);
        webInput.configureExecutionInput((input, inputBuilder) ->
                inputBuilder
                        .graphQLContext(contextBuilder -> contextBuilder.put("Authorization", authorization))
                        .build()
        );
        return chain.next(webInput);
    }
}

With that, you can fetch that value from the GraphQL context:

    @QueryMapping
    public String greeting(GraphQLContext context) {
        String authorization = context.getOrDefault("Authorization", "default");
        return "Hello, "   authorization;
    }
  • Related