Spring 使用 WebMvc 获取请求的 GraphQL headers

Spring GraphQL with WebMvc getting request headers

我有一个 Spring GraphQL 项目。每个数据获取器 (@SchemaMapping) 将从受身份验证保护的远程 API 获取数据。

我需要将授权 header 从原始请求(我可以在 @QueryMapping 方法中看到)传播到数据获取器。

在数据获取器中,我可以使用 RequestContextHolder 来获取请求和 headers,如下所示:

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

这可行,但我担心它会损坏。 Spring GraphQL documentation 指出:

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.

我尝试添加一个 ThreadLocalAccessor 组件,但在我看来,通过调试和阅读源代码,restoreValue 方法仅在 WebFlux 项目中被调用。

如何确保在 WebMvc 项目中得到正确的 RequestContextHolder

更新

我将添加一些代码以更好地解释我的用例。

CurrentActivity 是 parent 实体,而 Booking 是 child 实体。

我需要从 API 受身份验证保护的后端获取实体。我在原始请求(带有 graphql 查询的请求)中收到了授权令牌。

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
                )
            }
        }
    }
}

ThreadLocalAccessor 概念的真正含义是在一个环境中 store/restore 上下文值的一种方式,在该环境中可以在不同的线程上异步执行 如果没有其他基础架构已经支持那.

在 Spring WebFlux 的情况下,Reactor 上下文已经存在并填补了这个角色。 WebFlux 应用程序应该使用反应式 DataFetchersthe Reactor Context natively.

ThreadLocalAccessor 实现是 mostly useful for Spring MVC apps。任何 ThreadLocalAccessor 个 bean 都会被启动器 auto-configured。

在你的情况下,you could follow one of the samples和有类似的安排:

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.

请注意,仅当当前线程不是最初提取的值时才会调用 restoreValue(无需执行任何操作,值已在 ThreadLocal 中)。

我已经成功测试了这种方法,从 RequestContextHolder 获得了“授权”HTTP header 值。看来您尝试这种方法没有成功 - 您可以尝试使用 1.0.0-M3 并让我们知道它是否不起作用吗? You can create an issue on the project 使用 link 重现问题的示例项目。

备用解决方案

如果您不想处理 ThreadLocal 绑定值,您始终可以使用 WebInterceptor 来增加 GraphQLContext 自定义值。

这是一个例子:

@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);
    }
}

这样,您就可以从 GraphQL 上下文中获取该值:

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