Spring 引导、Freemarker、MVC 单元测试、Csrf
Spring Boot, Freemarker, MVC Unit Tests, Csrf
我正在使用 Freemarker Spring 启动并进行 mvc 单元测试。在我的 freemarker 模板中,我有一个隐藏的 csrf 令牌输入字段,如下所示:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
那我还有个mvc单元测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles("test")
public class MvcTests {
@Autowired
WebApplicationContext ctx;
MockMvc mvc;
HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository;
@Before
public void setUp() throws Exception {
mvc = webAppContextSetup(ctx).build();
httpSessionCsrfTokenRepository = new HttpSessionCsrfTokenRepository();
}
@Test
public void testInValidVoucherNumber() throws Exception {
CsrfToken csrfToken = httpSessionCsrfTokenRepository.generateToken(new MockHttpServletRequest());
mvc.perform(post("/voucher/claim")
.sessionAttr("_csrf.parameterName", "_csrf")
.sessionAttr("_csrf.token", csrfToken.getToken())
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("code", "ABC")
.param("_csrf", csrfToken.getToken()))
.andExpect(status().isOk())
.andExpect(view().name("claim-voucher"))
.andExpect(content().string(containsString("Code is invalid")));
}
}
当我 运行 单元测试时,我得到以下 freemarker 错误:
testInValidVoucherNumber(MvcTests) Time elapsed: 0.904 sec <<< ERROR!
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> _csrf [in template "claim-voucher.ftl" at line 25, column 34]
----
Tip: If the failing expression is known to be legally refer to something that's null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: ${_csrf.parameterName} [in template "claim-voucher.ftl" at line 25, column 32]
~ Reached through: #nested [in template "layout.ftl" in macro "basic" at line 16, column 9]
这是控制器:
@RequestMapping(value = "/voucher/claim", method = RequestMethod.POST)
public String checkVoucherCode(@Valid ClaimVoucherForm claimVoucherForm, Model model) {
//Business logic
}
似乎 freemarker 没有访问 csrf parameterName 和 csrf token 的权限。有没有办法在不改变控制器的情况下包含csrf信息或者单元测试有问题?
会话属性在带有 Session.
前缀的模型中可用。您的模板需要更新为如下内容:
<input type="hidden" name="${Session._csrf.parameterName}" value="${Session._csrf.token}"/>
您还在测试中错误地配置了会话属性。 _csrf.token
意味着 Freemarker 将查找名为 _csrf
的对象,然后在该对象上查找名为 token
的属性。您需要分配一个名为 _csrf
的会话属性,其中包含 parameterName
和 token
:
Map<String, Object> csrf = new HashMap<String, Object>();
csrf.put("parameterName", "_csrf");
csrf.put("token", csrfToken.getToken());
mvc.perform(post("/voucher/claim")
.sessionAttr("_csrf", csrf)
而不是:
.sessionAttr("_csrf.parameterName", "_csrf")
.sessionAttr("_csrf.token", csrfToken.getToken())
尝试:
.sessionAttr("_csrf", csrfToken)
当你写 ${_csrf.parameterName}
时,FreeMarker 在模型变量“_csrf”上查找字段 "parameterName"。不适用于模型变量“_csrf.parameterName”。
也就是说,您必须在测试中将这些参数添加为会话属性,这很奇怪。 Spring Security 的 CsrfFilter 不应该为您做这件事吗?
我使用
解决了同样的问题
@Import({CsrfFilter.class, HttpSessionCsrfTokenRepository.class})
在我的测试 class 之上并且没有使用任何 .sessionAttr()
调用。
我正在使用 Freemarker Spring 启动并进行 mvc 单元测试。在我的 freemarker 模板中,我有一个隐藏的 csrf 令牌输入字段,如下所示:
<input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}"/>
那我还有个mvc单元测试:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = MyApplication.class)
@WebAppConfiguration
@ActiveProfiles("test")
public class MvcTests {
@Autowired
WebApplicationContext ctx;
MockMvc mvc;
HttpSessionCsrfTokenRepository httpSessionCsrfTokenRepository;
@Before
public void setUp() throws Exception {
mvc = webAppContextSetup(ctx).build();
httpSessionCsrfTokenRepository = new HttpSessionCsrfTokenRepository();
}
@Test
public void testInValidVoucherNumber() throws Exception {
CsrfToken csrfToken = httpSessionCsrfTokenRepository.generateToken(new MockHttpServletRequest());
mvc.perform(post("/voucher/claim")
.sessionAttr("_csrf.parameterName", "_csrf")
.sessionAttr("_csrf.token", csrfToken.getToken())
.contentType(MediaType.APPLICATION_FORM_URLENCODED)
.param("code", "ABC")
.param("_csrf", csrfToken.getToken()))
.andExpect(status().isOk())
.andExpect(view().name("claim-voucher"))
.andExpect(content().string(containsString("Code is invalid")));
}
}
当我 运行 单元测试时,我得到以下 freemarker 错误:
testInValidVoucherNumber(MvcTests) Time elapsed: 0.904 sec <<< ERROR!
org.springframework.web.util.NestedServletException: Request processing failed; nested exception is freemarker.core.InvalidReferenceException: The following has evaluated to null or missing:
==> _csrf [in template "claim-voucher.ftl" at line 25, column 34]
----
Tip: If the failing expression is known to be legally refer to something that's null or missing, either specify a default value like myOptionalVar!myDefault, or use <#if myOptionalVar??>when-present<#else>when-missing</#if>. (These only cover the last step of the expression; to cover the whole expression, use parenthesis: (myOptionalVar.foo)!myDefault, (myOptionalVar.foo)??
----
----
FTL stack trace ("~" means nesting-related):
- Failed at: ${_csrf.parameterName} [in template "claim-voucher.ftl" at line 25, column 32]
~ Reached through: #nested [in template "layout.ftl" in macro "basic" at line 16, column 9]
这是控制器:
@RequestMapping(value = "/voucher/claim", method = RequestMethod.POST)
public String checkVoucherCode(@Valid ClaimVoucherForm claimVoucherForm, Model model) {
//Business logic
}
似乎 freemarker 没有访问 csrf parameterName 和 csrf token 的权限。有没有办法在不改变控制器的情况下包含csrf信息或者单元测试有问题?
会话属性在带有 Session.
前缀的模型中可用。您的模板需要更新为如下内容:
<input type="hidden" name="${Session._csrf.parameterName}" value="${Session._csrf.token}"/>
您还在测试中错误地配置了会话属性。 _csrf.token
意味着 Freemarker 将查找名为 _csrf
的对象,然后在该对象上查找名为 token
的属性。您需要分配一个名为 _csrf
的会话属性,其中包含 parameterName
和 token
:
Map<String, Object> csrf = new HashMap<String, Object>();
csrf.put("parameterName", "_csrf");
csrf.put("token", csrfToken.getToken());
mvc.perform(post("/voucher/claim")
.sessionAttr("_csrf", csrf)
而不是:
.sessionAttr("_csrf.parameterName", "_csrf")
.sessionAttr("_csrf.token", csrfToken.getToken())
尝试:
.sessionAttr("_csrf", csrfToken)
当你写 ${_csrf.parameterName}
时,FreeMarker 在模型变量“_csrf”上查找字段 "parameterName"。不适用于模型变量“_csrf.parameterName”。
也就是说,您必须在测试中将这些参数添加为会话属性,这很奇怪。 Spring Security 的 CsrfFilter 不应该为您做这件事吗?
我使用
解决了同样的问题@Import({CsrfFilter.class, HttpSessionCsrfTokenRepository.class})
在我的测试 class 之上并且没有使用任何 .sessionAttr()
调用。