领先一步
VMware 提供培训和认证,以加速您的进步。
了解更多[侧边栏 title=2015年3月31日更新]此博客已过时,不再维护。请参阅参考文档的测试章节以获取更新的文档。[/侧边栏]
在我的上一篇博客中,我们演示了新的 Spring Security 测试支持如何简化基于方法的安全测试。在本博客中,我们将探讨如何将测试支持与 Spring MVC Test 一起使用。
为了将 Spring Security 与 Spring MVC Test 一起使用,必须添加 Spring Security 的 `FilterChainProxy` 作为 `Filter`。例如
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class CsrfShowcaseTests {
@Autowired
private WebApplicationContext context;
@Autowired
private Filter springSecurityFilterChain;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.addFilters(springSecurityFilterChain)
.build();
}
...
[侧边栏 title=源代码]您可以在 github 上找到本博客系列的完整源代码[/侧边栏]
Spring MVC Test 提供了一个名为 `RequestPostProcessor` 的便捷接口,可用于修改请求。Spring Security 提供了许多 `RequestPostProcessor` 实现,使测试更容易。为了使用 Spring Security 的 `RequestPostProcessor` 实现,请确保使用了以下静态导入
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;
在测试任何非安全 HTTP 方法并使用 Spring Security 的 CSRF 保护时,必须确保在请求中包含有效的 CSRF 令牌。在 Spring Security 测试支持之前,这非常具有挑战性。现在,您可以使用以下方法将有效的 CSRF 令牌指定为请求参数:
mvc
.perform(post("/").with(csrf()))
如果您愿意,也可以在标头中包含 CSRF 令牌:
mvc
.perform(post("/").with(csrf().asHeader()))
您还可以测试提供无效的 CSRF 令牌:
mvc
.perform(post("/").with(csrf().useInvalidToken()))
通常需要以特定用户身份运行测试。在 Spring Security 测试支持之前,这在基于 Web 的测试中具有挑战性,因为 `SecurityContextHolder` 由 `SecurityContextRepositoryFilter` 修改。
现在有两种简单的方法来填充用户
[侧边栏 title=注意] 使用 Spring Security 的无状态模式时,当前用户身份运行的测试支持当前不起作用。这在 SEC-2593 中进行跟踪。[/侧边栏]
有很多选项可用于填充测试用户。例如,以下操作将以用户名为“user”,密码为“password”,角色为“ROLE_USER”的用户身份运行(该用户无需存在):
mvc
.perform(get("/").with(user("user")))
您可以轻松进行自定义。例如,以下操作将以用户名为“admin”,密码为“pass”,角色为“ROLE_USER”和“ROLE_ADMIN”的用户身份运行(该用户无需存在)。
mvc
.perform(get("/admin").with(user("admin").password("pass").roles("USER","ADMIN")))
如果您有要使用的自定义 `UserDetails`,您也可以轻松指定它。例如,以下操作将使用指定的 `UserDetails`(无需存在)以具有指定 `UserDetails` 主体的 `UsernamePasswordAuthenticationToken` 运行:
mvc
.perform(get("/").with(user(userDetails)))
如果您想要自定义的 `Authentication`(无需存在),您可以使用以下方法:
mvc
.perform(get("/").with(authentication(authentication)))
您甚至可以使用以下方法自定义 `SecurityContext`:
mvc
.perform(get("/").with(securityContext(securityContext)))
我们还可以通过使用 `MockMvcBuilders` 的默认请求来确保以特定用户身份运行每个请求。例如,以下操作将以用户名为“admin”,密码为“password”,角色为“ROLE_ADMIN”的用户身份运行(该用户无需存在):
mvc = MockMvcBuilders
.webAppContextSetup(context)
.defaultRequest(get("/").with(user("user").roles("ADMIN")))
.addFilters(springSecurityFilterChain)
.build();
如果您发现自己在许多测试中使用相同的用户,建议将用户移动到方法中。例如,您可以在名为 `CustomSecurityMockMvcRequestPostProcessors` 的自定义类中指定以下内容:
public static RequestPostProcessor rob() {
return user("rob").roles("ADMIN");
}
现在,您可以对 `SecurityMockMvcRequestPostProcessors` 执行静态导入,并在测试中使用它:
import static sample.CustomSecurityMockMvcRequestPostProcessors.*;
...
mvc
.perform(get("/").with(rob()))
作为使用 `RequestPostProcessor` 创建用户的替代方法,您可以使用我之前博客文章中描述的注解。但是,要使此方法有效,必须指定 `RequestPostProcessor` 使用注解中的用户运行请求,如下所示:
mvc = MockMvcBuilders
.webAppContextSetup(context)
.addFilters(springSecurityFilterChain)
.defaultRequest(get("/").with(testSecurityContext()))
.build();
[侧边栏 title=注意] 不要忘记按照之前的博客中所述指定 `WithSecurityContextTestExcecutionListener`。[/侧边栏]
现在,您可以使用基于方法的安全测试中描述的任何方法运行测试。例如,以下操作将使用用户名为“user”,密码为“password”,角色为“ROLE_USER”的用户运行测试:
@Test
@WithMockUser
public void requestProtectedUrlWithUser() throws Exception {
mvc
.perform(get("/"))
...
}
或者,以下操作将使用用户名为“user”,密码为“password”,角色为“ROLE_ADMIN”的用户运行测试:
@Test
@WithMockUser(roles="ADMIN")
public void requestProtectedUrlWithUser() throws Exception {
mvc
.perform(get("/"))
...
}
有关如何使用注解的更多信息,请参阅我的上一篇博客。
虽然始终可以使用 HTTP 基本身份验证进行身份验证,但记住标头名称、格式和编码值有点繁琐。现在可以使用 Spring Security 的 `httpBasic` `RequestPostProcessor` 来完成此操作。例如,下面的代码片段:
mvc
.perform(get("/").with(httpBasic("user","password")))
将尝试使用 HTTP 基本身份验证来验证用户名为“user”和密码为“password”的用户,方法是确保在 HTTP 请求上填充以下标头:
Authorization: Basic dXNlcjpwYXNzd29yZA==
Spring MVC Test 还提供了一个 `RequestBuilder` 接口,可用于创建测试中使用的 `MockHttpServletRequest`。Spring Security 提供了一些 `RequestBuilder` 实现,可用于简化测试。为了使用 Spring Security 的 `RequestBuilder` 实现,请确保使用了以下静态导入:
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;
您可以轻松创建请求以使用 Spring Security 的测试支持来测试基于表单的身份验证。例如,以下操作将使用用户名“user”、密码“password”和有效的 CSRF 令牌向“/login”提交 POST 请求。
mvc
.perform(formLogin())
自定义请求很容易。例如,以下操作将使用用户名“admin”、密码“pass”和有效的 CSRF 令牌向“/auth”提交 POST 请求。
mvc
.perform(formLogin("/auth").user("admin").password("pass"))
我们还可以自定义包含用户名和密码的 HTTP 参数名称。例如,这是上面请求的修改版本,用户名包含在 HTTP 参数“u”中,密码包含在 HTTP 参数“p”中。
mvc
.perform(formLogin("/auth").user("a","admin").password("p","pass"))
虽然使用标准 Spring MVC Test 非常简单,但您可以使用 Spring Security 的测试支持来简化注销测试。例如,以下操作将使用有效的 CSRF 令牌向“/logout”提交 POST 请求。
mvc
.perform(logout())
您还可以自定义要发布到的 URL。例如,下面的代码段将使用有效的 CSRF 令牌向“/signout”提交 POST 请求。
mvc
.perform(logout("/signout"))
有时需要对请求进行各种与安全相关的断言。为了满足此需求,Spring Security 测试支持实现了 Spring MVC Test 的ResultMatcher
接口。为了使用 Spring Security 的ResultMatcher
实现,请确保使用了以下静态导入
import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;
有时,断言MockMvc
调用的结果没有关联的已认证用户可能很有价值。例如,您可能希望测试提交无效的用户名和密码,并验证没有用户经过身份验证。您可以使用 Spring Security 的测试支持轻松地执行此操作,例如以下操作
mvc
.perform(formLogin().password("invalid"))
.andExpect(unauthenticated());
我们经常需要断言存在已认证的用户。例如,我们可能需要验证我们是否成功进行了身份验证。我们可以使用以下代码段验证基于表单的登录是否成功。
mvc
.perform(formLogin())
.andExpect(authenticated());
如果我们想断言用户的角色,我们可以改进之前的代码,如下所示。
mvc
.perform(formLogin().user("admin"))
.andExpect(authenticated().withRoles("USER","ADMIN"));
或者,我们可以验证用户名。
mvc
.perform(formLogin().user("admin"))
.andExpect(authenticated().withUsername("admin"));
我们也可以组合这些断言。
mvc
.perform(formLogin().user("admin").roles("USER","ADMIN"))
.andExpect(authenticated().withUsername("admin"));
我们现在已经了解了 Spring Security 测试支持如何使 Spring MVC Test 的测试更容易。在我们的下一篇文章中,我们将探讨如何将 Spring Security 测试支持与Spring Test MVC HtmlUnit一起使用。
[边栏标题=请提供反馈!]如果您对本博客系列或 Spring Security 测试支持有任何反馈,我鼓励您通过JIRA、评论部分或在 Twitter 上联系我@rob_winch。当然,最好的反馈是贡献。[/边栏]