A security integration test must exercise every layer named in its claim: decoder, filter chain, managed service and scoped query.
Test Spring JWT authorization through filters, service proxy and SQL
The fixture installs actual local components
JwtTenantBoundaryTest creates a Spring web application context, installs springSecurity() on MockMvc, registers a JWT decoder, and calls a managed @PreAuthorize reader backed by JdbcTemplate. It checks success for the matching tenant, 403 for wrong scope, 403 for wrong tenant, 401 for missing tenant claim, and distinct H2 rows under one receipt ID. A direct call to the target method would miss both filters and the proxy rule.
This is broader than standalone MockMvc, which registers a controller directly. It is still a local context, not the site's full Boot application component scan or a live server. The signing keys are generated in the test, not discovered through remote JWKS. The failure-layer lesson explains how to state that evidence honestly.
Keep negative cases tied to state
The test's denied reads return no protected value. For a write, also inspect the database afterward to prove that no mutation occurred. Add an untrusted-header case because a path-only denial can miss an identity override. Before release, repeat these checks against the deployed route, actual issuer and target database, including issuer outage and key rotation.
Checked source
client = MockMvcBuilders.webAppContextSetup(context)
.apply(springSecurity()).build();
client.perform(get("/contract/tenant/tenant-west")
.header("Authorization", "Bearer " + eastToken))
.andExpect(status().isForbidden());Verification boundary
JwtTenantBoundaryTest.trustedTenantClaimPassesTheMatchingServiceRule, JwtTenantBoundaryTest.wrongScopeIsForbiddenBeforeTheService, JwtTenantBoundaryTest.anotherTenantIsForbiddenAfterAuthentication and JwtTenantBoundaryTest.missingTenantClaimIsRejectedByTheDecoder runs in the downloadable Spring source kit. The excerpt is shortened; the kit contains the complete test.
Costs and limits
The five local tests create an in-memory H2 database and web context per method. They cost more than a plain unit test but omit remote issuer discovery, application deployment, gateway behavior, real database grants and write-side effects. No performance or production trust claim follows from their speed.
Common Mistakes
- Do not call a direct service-object test proof of a security filter.
- Do not call MockMvc proof of a deployed proxy path.
- Do not infer database safety from a 403 without checking the protected state.
Read next
Spring Security JWT tenant principal: map only a validated claim, Spring Security scope versus tenant ownership: two separate decisions, Spring JdbcTemplate tenant predicates: put ownership in the SQL query, Spring MockMvc standalone tests: know which HTTP layers were assembled.
Continue with checked tenant commands
Continue with Spring command rollback test: inspect state after an injected failure.
