MockMvc在执行请求后似乎是清晰的SecurityContext(java.lang.IllegalArgumentException:Authentication对象不能为null)
我正在尝试使用SpringBoot+SpringDataMongo+SpringMVC运行一些集成测试 我已经简化并泛化了代码,但是它应该能够通过下面的测试重现行为 从MockMvc在执行请求后似乎是清晰的SecurityContext(java.lang.IllegalArgumentException:Authentication对象不能为null),java,spring,spring-data,spring-data-mongodb,mockmvc,Java,Spring,Spring Data,Spring Data Mongodb,Mockmvc,我正在尝试使用SpringBoot+SpringDataMongo+SpringMVC运行一些集成测试 我已经简化并泛化了代码,但是它应该能够通过下面的测试重现行为 从BookRepository界面可以看到,我希望用户能够只检索他拥有的书籍(@Query(“{ownerName:”?#{principal?.username})),我正在编写一个测试,以执行POST来保存一本书,然后验证该书是否正确设置了所有者 为了回答这里的问题,我将测试简化为一个GET,然后调用findAll() 问题 在
BookRepository
界面可以看到,我希望用户能够只检索他拥有的书籍(@Query(“{ownerName:”?#{principal?.username})
),我正在编写一个测试,以执行POST
来保存一本书,然后验证该书是否正确设置了所有者
为了回答这里的问题,我将测试简化为一个GET
,然后调用findAll()
问题
在执行任何MockMvc
请求后,使用ThreadLocalSecurityContextHolderStrategy\35; clearContext()
清除SecurityContext
,这会导致在我尝试调用repository.findAll();
java.lang.IllegalArgumentException:身份验证对象不能为null
BookRepository.java
我认为您可以手动配置
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();
}
// ...
}
作为缔约国:
为了在Spring MVC测试中使用Spring Security,有必要添加Spring Security FilterChainProxy作为过滤器。还需要添加Spring Security的TestSecurityContextHolderPostProcessor,以支持在带有注释的Spring MVC测试中以用户身份运行。这可以使用Spring Security的SecurityLockMVCConfigure完成rs.springSecurity()
您的案例不起作用,因为SecurityContextPersistenceFilter和FilterChainProxy筛选器清除了SecurityContextHolder,但
TestSecurityContextHolder
(由用SecurityContextTestExecutionListener填写)仍然包含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();
}
您可能忘记用@controller
注释标记任何控制器。在我的情况下发生了这种情况,修复它有助于修复错误。这可能是故障排除步骤之一
发生这种情况的原因是,当您在运行时不使用@controller
标记控制器并尝试从模板语言(在我的例子中是Thymeleaf)进行引用时,它会在上下文中向下延伸并返回丢失身份验证对象,因此会出现如下错误:
Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "#authorization.expression('!isAuthenticated()')" (template: "fragments/layout" - line 64, col 8)
at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
... 47 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('!isAuthenticated()')" (template: "fragments/layout" - line 64, col 8)
at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290)
...
...
...
...
... 49 more
Caused by: java.lang.IllegalArgumentException: Authentication object cannot be null
at org.springframework.security.access.expression.SecurityExpressionRoot.<init>(SecurityExpressionRoot.java:61)
原因:org.attoparser.ParseException:异常评估SpringEL表达式:“#authorization.expression(“!isAuthenticated()”)”(模板:“片段/布局”-第64行,第8列)
位于org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
位于org.attoparser.MarkupParser.parse(MarkupParser.java:257)
位于org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
…还有47个
原因:org.thymeleaf.exceptions.TemplateProcessingException:异常评估SpringEL表达式:“#authorization.expression(“!isAuthenticated()”)”(模板:“片段/布局”-第64行,第8列)
位于org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290)
...
...
...
...
…还有49个
原因:java.lang.IllegalArgumentException:身份验证对象不能为null
位于org.springframework.security.access.expression.SecurityExpressionRoot.(SecurityExpressionRoot.java:61)
希望这有帮助。我刚刚找到了一个很好的解决方案。您可以在测试配置中注册一个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());
});
}
公共类MockMvcTestSecurityContextPropagationCustomizer实现MockMvcBuilderCustomizer{
@凌驾
public void自定义(ConfigurableMockMvcBuilder builder){
builder.alwaysDo(结果->{
调试(“将SecurityContextHolder重置为TestSecurityContextHolder”);
setContext(TestSecurityContextHolder.getContext());
});
}
}
[spring boot]整个安全过滤器链都在那里,事实上,当我调用repository.findAll()时,身份验证上下文就在那里.但是MockMvc出于某种原因清理了它,这使得一个完全合理的测试用例无法执行。无论如何,我尝试了你的方法,但我得到了相同的错误。非常感谢你尝试帮助hi@seregamorph,而不是你的建议。这是我迄今为止看到的最好的解决方法,所以我将投票支持它。但是,我不相信manuaLLY在与MockMvc进行交互后设置安全上下文,这是一个非常可读的测试,所以我不认为它是一个明确的解决方案。
Caused by: org.attoparser.ParseException: Exception evaluating SpringEL expression: "#authorization.expression('!isAuthenticated()')" (template: "fragments/layout" - line 64, col 8)
at org.attoparser.MarkupParser.parseDocument(MarkupParser.java:393)
at org.attoparser.MarkupParser.parse(MarkupParser.java:257)
at org.thymeleaf.templateparser.markup.AbstractMarkupTemplateParser.parse(AbstractMarkupTemplateParser.java:230)
... 47 more
Caused by: org.thymeleaf.exceptions.TemplateProcessingException: Exception evaluating SpringEL expression: "#authorization.expression('!isAuthenticated()')" (template: "fragments/layout" - line 64, col 8)
at org.thymeleaf.spring5.expression.SPELVariableExpressionEvaluator.evaluate(SPELVariableExpressionEvaluator.java:290)
...
...
...
...
... 49 more
Caused by: java.lang.IllegalArgumentException: Authentication object cannot be null
at org.springframework.security.access.expression.SecurityExpressionRoot.<init>(SecurityExpressionRoot.java:61)
public class MockMvcTestSecurityContextPropagationCustomizer implements MockMvcBuilderCustomizer {
@Override
public void customize(ConfigurableMockMvcBuilder<?> builder) {
builder.alwaysDo(result -> {
log.debug("resetting SecurityContextHolder to TestSecurityContextHolder");
SecurityContextHolder.setContext(TestSecurityContextHolder.getContext());
});
}