Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

MockMvc seems to be clear SecurityContext after performing request (java.lang.IllegalArgumentException: Authentication object cannot be null)

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()

Problem

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

BookRepository.java

@RepositoryRestResource
public interface BookRepository extends MongoRepository<Book, String> {
      
    @Query("{ 'ownerName' : ?#{principal?.username} }")
    List<Book> findAll();  
 
}

BookCustomRepositoryIntegrationTest.java

/**
 * 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


    }

}
like image 395
snovelli Avatar asked Dec 24 '22 05:12

snovelli


2 Answers

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();
}
like image 77
seregamorph Avatar answered Dec 28 '22 07:12

seregamorph


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]

like image 26
Stefan Avatar answered Dec 28 '22 08:12

Stefan