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