MockMvc在执行请求后似乎清除了SecurityContext(java.lang.IlLegalArgumentException:身份验证对象不能为空)

zf9nrax1  于 2022-10-04  发布在  Java
关注(0)|答案(4)|浏览(277)

我正在尝试使用SpringBoot+Spring data Mongo+SpringMVC运行一些集成测试

我已经简化和泛化了代码,但它应该能够通过以下测试重现该行为。

正如您从BookRepository界面中看到的,我希望用户只能检索他拥有的图书(@Query("{ 'ownerName' : '?#{principal?.username})),并且我正在编写一个测试来执行POST来保存图书,然后验证图书的所有者设置是否正确。

出于此处问题的目的,我已将测试简化为GET,然后调用findAll()

问题

执行任何MockMvc请求后,使用ThreadLocalSecurityContextHolderStrategy#clearContext()清除SecurityContext,这会在我尝试调用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

    }

}
am46iovg

am46iovg1#

您的案例不起作用,因为SecurityContextPersistenceFilter和FilterChainProxy筛选器清除了SecurityConextHolder,但TestSecurityContextHolder(由WithSecurityContextTestExecutionListener填充)仍包含SecurityContext。

尝试以下方法:

@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();
}
j2cgzkjk

j2cgzkjk2#

我刚刚找到了一个很好的解决这个问题的办法。您可以在测试配置中注册MockMvcBuilderCustomizer Bean,一切都很正常。

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]

nvbavucw

nvbavucw3#

我认为可以手动配置MockMvc,并按如下方式配置Spring安全性,而不是使用AutoConfigureMockMvc注解:

import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;

@RunWith(SpringRunner.class)
@SpringBootTest
public class BookCustomRepositoryIntegrationTest {

    @Before
    public void setup() {
        mockMvc = MockMvcBuilders
                .webAppContextSetup(context)
                .apply(springSecurity()) 1
                .build();
    }
    // ...
}

正如documentation所述:

为了在Spring MVC测试中使用Spring Security,有必要添加Spring Security FilterChainProxy作为筛选器。还需要添加Spring Security的TestSecurityContextHolderPostProcessor,以支持在带有注解的Spring MVC测试中以用户身份运行。这可以使用Spring Security的SecurityMockMvcConfigurers.springSecurity()来完成。

vkc1a9a2

vkc1a9a24#

一种好的做法是将RestController的测试与应用程序的其他层的测试分开。在您的情况下,我认为您不应该将REST调用测试与资源库调用测试混为一谈。

我想您应该进行一个模拟的MVC调用来测试您的“repository.findAll()”

我使用的是Spring Boot 2.6.7(Spring-Security-test-5.6.3.jar)和JUnit5,即使在执行MockMVC http请求之后,TestSecurityConextHolder也会保存安全上下文。

@ExtendWith(SpringExtension.class)
@SpringBootTest(classes = MyApp.class, webEnvironment = WebEnvironment.RANDOM_PORT)
@AutoConfigureMockMvc
public class MyTests {

...

    @Test
    @WithMockUser
    public void test_make_many_mockmvc_calls() {

            //The first call
            mockMvc.perform(MockMvcRequestBuilders.get("/books")//
                .contentType(MediaType.APPLICATION_JSON)//
                .accept(MediaType.APPLICATION_JSON))//
                .andExpect(MockMvcResultMatchers.status().isOk());

            //Another call
            mockMvc.perform(MockMvcRequestBuilders.get("/books")//
                .contentType(MediaType.APPLICATION_JSON)//
                .accept(MediaType.APPLICATION_JSON))//
                .andExpect(MockMvcResultMatchers.status().isOk());
    }
...
}

但是如果你没有一个好的变通办法,你需要添加一些东西..事实上,您的repository.findAll()将使用SecurityEvaluationConextExtension来解析安全信息,该扩展使用主SecurityConextHolder(而不是TestSecurityConextHolder)。

所以我想你应该做这样的东西:

public class MyCustomSecurityEvaluationContextExtension implements EvaluationContextExtension {
...
    @Override
    public SecurityExpressionRoot getRootObject() {

        //Override the way you retrieve the Authentication object
    }
...
}

相关问题