-
Notifications
You must be signed in to change notification settings - Fork 319
Description
I've recently been asked to improve some security and testing of another team's application. They provide both a WebMVC and Spring for GraphQL API. As part of our security improvements, we layer another OncePerRequestFilter into WebMVC. This filter just creates some additional ThreadLocal context information (built upon Spring Security Jwt authentication) that helps our applications abstract away some of the complexity of an always changing security landscape that we face.
For our other applications, it has been easy to write tests that use MockMvc and Spring AddOns OAuth2 Test Support's @WithJwt
test annotation. We have standardized personas that provide the minimal JWT we need to test our production Spring Security and custom abstractions.
As part of the process of updating this application, I have a test that makes use of the @AutoConfigureMockMvc
to call a WebMVC endpoint. This simulates a user uploading new content to the application. Spring Security and our filter work fine. Here's a snippet of code followed by the logs that are generated:
// plan ingest
val importRequest = MockMvcRequestBuilders.multipart("/plans/import")
.file(planFile)
.contentType(MediaType.APPLICATION_JSON)
.with(SecurityMockMvcRequestPostProcessors.csrf())
val result = mockMvc.perform(importRequest).andExpect(MockMvcResultMatchers.status().isCreated).andReturn()
2025-07-17T14:04:01.713-04:00 TRACE 24832 --- [APP] [ Test worker] w.c.CustomContextHolderFilter : doFilterInternal: /plans/import
2025-07-17T14:04:01.738-04:00 TRACE 24832 --- [APP] [ Test worker] w.c.CustomContextHolderFilter : kradosUserProfileContext: app.security.context.OAuth2ResourceServerCustomContext@384e51f5
2025-07-17T14:04:01.746-04:00 INFO 24832 --- [APP] [ Test worker] a.filter.RequestLoggingFilter : Before request [POST /plans/import, client=127.0.0.1, user=local-user]
2025-07-17T14:04:05.893-04:00 WARN 24832 --- [APP] [ Test worker] a.s.c.plan.PlanController : plan upload time: PT4.113483S
The next step of the test is to then make a GraphQL query to return some information about the new content. Here's the test code snippet and the logs:
// graphql all queries for the plan slug
val client = MockMvcWebTestClient.bindToApplicationContext(context)
.apply(SecurityMockMvcConfigurers.springSecurity())
.defaultRequest(MockMvcRequestBuilders.post("/graphql").with(SecurityMockMvcRequestPostProcessors.csrf()))
.configureClient()
.baseUrl("/graphql")
.build()
val graphQlTester = HttpGraphQlTester.create(client);
val importResultData: PartialImportResultData = jacksonObjectMapper().readValue(result.response.contentAsString)
val slug = importResultData.slug
val graphQlResponse = graphQlTester
.documentName("planDetails")
.variable("planSlug", slug)
.execute()
2025-07-17T14:04:06.101-04:00 WARN 24832 --- [APP] [ Test worker] a.CustomContextAccessor : >>>>**** getValue: app.security.context.EmptyCustomContext@a1ce033
2025-07-17T14:04:06.332-04:00 WARN 24832 --- [APP] [ Test worker] a.CustomContextAccessor : >>>>**** setValue: app.security.context.EmptyCustomContext@a1ce033
In this case, in the logs, I can see my ThreadLocalAssessor implementation running to indicate that the GraphQL engine is asynchronously loading data from the DataFetcher. However, the EmptyCustomContext
is indicative that my CustomContextHolder
was never set. This means that the CustomContextHolderFilter
was never executed. Which we can see is missing from the logs.
I'm guessing this has something to do with the fact that the application is WebMVC and not WebFlux, as I tried to @AutoConfigureWebTestClient
and couldn't get any of the Spring Security test processors like csrf()
or springSecurity()
to apply to it. It was because of NULL httpHandlerBuilder
and similar. That is also why I cannot use the MockMvcWebTestClient.bindTo(mockMvc)
.
The other possibility is that the GraphQL endpoint is in a different application context? And therefore, my filters aren't being applied to it at all?
I'd appreciate any guidance you can provide on this problem. Unfortunately, I cannot provide access to the actual code. And, unless really necessary, would like to avoid writing up a smaller example project, as it will probably still take significant time.