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 应用程序应该使用反应式 DataFetchers
和 the Reactor Context natively.
ThreadLocalAccessor
实现是 mostly useful for Spring MVC apps。任何 ThreadLocalAccessor
个 bean 都会被启动器 auto-configured。
在你的情况下,you could follow one of the samples和有类似的安排:
- 声明一个Servlet filter that extracts the header value and set it as a request attribute with a well-known name
- 创建 ThreadLocalAccessor component and use it to store request attributes into the context
- Fetch the relevant attribute from your
DataFetcher
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;
}
我有一个 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 应用程序应该使用反应式 DataFetchers
和 the Reactor Context natively.
ThreadLocalAccessor
实现是 mostly useful for Spring MVC apps。任何 ThreadLocalAccessor
个 bean 都会被启动器 auto-configured。
在你的情况下,you could follow one of the samples和有类似的安排:
- 声明一个Servlet filter that extracts the header value and set it as a request attribute with a well-known name
- 创建 ThreadLocalAccessor component and use it to store request attributes into the context
- Fetch the relevant attribute from your
DataFetcher
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;
}