I'm trying to run some integration test using SpringBoot + Spring Data Mongo + SpringMVC
I've simplified and generified the code but it should be able to reproduce the behavior with the following test.
As you can see from BookRepository
interface I want the user to be able to retrieve only the books that he owns (@Query("{ 'ownerName' : '?#{principal?.username})
) and I'm writing a test to perform a POST
to save a Book and then verify the book has the owner set appropriately.
For the purpose of the question here I've simplified the test to just to a GET
and then calling findAll()
After performing any MockMvc
request, the SecurityContext
is cleared using ThreadLocalSecurityContextHolderStrategy#clearContext()
which cause the following exception to be thrown when I try to call repository.findAll();
java.lang.IllegalArgumentException: Authentication object cannot be null
@RepositoryRestResource
public interface BookRepository extends MongoRepository<Book, String> {
@Query("{ 'ownerName' : ?#{principal?.username} }")
List<Book> findAll();
}
/**
* Integrate data mongo + mvc
*/
@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc
public class BookCustomRepositoryIntegrationTest {
@Autowired
BookRepository repository;
@Autowired
MockMvc mockMvc;
@Test
@WithMockUser
public void reproduceBug() throws Exception {
repository.findAll(); //Runs allright
mockMvc.perform(get("/books")
.contentType(APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
repository.findAll(); //Throws exception: java.lang.IllegalArgumentException: Authentication object cannot be null
}
}
Your case does not work, because SecurityContextPersistenceFilter and FilterChainProxy filters clear SecurityContextHolder, but the TestSecurityContextHolder
(filled by WithSecurityContextTestExecutionListener
) still contains SecurityContext.
Try this approach:
@Test
@WithMockUser
public void reproduceBug() throws Exception {
repository.findAll();
mockMvc.perform(get("/books")
.contentType(APPLICATION_JSON_UTF8))
.andExpect(status().isOk());
SecurityContextHolder.setContext(TestSecurityContextHolder.getContext());
repository.findAll();
}
I just now found a nice solution to this problem. You can register a MockMvcBuilderCustomizer bean in your test configuration and all works fine.
public class MockMvcTestSecurityContextPropagationCustomizer implements MockMvcBuilderCustomizer {
@Override
public void customize(ConfigurableMockMvcBuilder<?> builder) {
builder.alwaysDo(result -> {
log.debug("resetting SecurityContextHolder to TestSecurityContextHolder");
SecurityContextHolder.setContext(TestSecurityContextHolder.getContext());
});
}
}
[spring-boot]
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With