12. Spring MVC 测试集成

Spring Security 提供与Spring MVC 测试的全面集成

12.1 设置 MockMvc 和 Spring Security

为了将 Spring Security 与 Spring MVC Test 一起使用,有必要将 Spring Security FilterChainProxy添加为Filter。还必须添加 Spring Security 的TestSecurityContextHolderPostProcessor以支持在带有 Comments 的 Spring MVC 测试中以用户身份运行。这可以使用 Spring Security 的SecurityMockMvcConfigurers.springSecurity()完成。例如:

Note

Spring Security 的测试支持需要 spring-test-4.1.3.RELEASE 或更高版本。

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

@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class CsrfShowcaseTests {

	@Autowired
	private WebApplicationContext context;

	private MockMvc mvc;

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

...
  • (1) SecurityMockMvcConfigurers.springSecurity()将执行将 Spring Security 与 Spring MVC Test 集成在一起所需的所有初始设置

12.2 SecurityMockMvcRequestPostProcessors

Spring MVC Test 提供了一个方便的接口,称为RequestPostProcessor,可用于修改请求。 Spring Security 提供了许多RequestPostProcessor实现,这些实现使测试更加容易。为了使用 Spring Security 的RequestPostProcessor实现,请确保使用以下静态导入:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;

12.2.1 使用 CSRF 保护进行测试

当测试任何非安全的 HTTP 方法并使用 Spring Security 的 CSRF 保护时,必须确保在请求中包括有效的 CSRF 令牌。使用以下命令将有效的 CSRF 令牌指定为请求参数:

mvc
	.perform(post("/").with(csrf()))

如果愿意,可以在标题中包含 CSRF 令牌:

mvc
	.perform(post("/").with(csrf().asHeader()))

您还可以使用以下方法测试提供的 CSRF 令牌无效:

mvc
	.perform(post("/").with(csrf().useInvalidToken()))

12.2.2 在 Spring MVC 测试中以用户身份运行测试

通常需要以特定用户身份运行测试。填充用户有两种简单的方法:

12.2.3 在 Spring MVC Test 中使用 RequestPostProcessor 以用户身份运行

有许多选项可用于将用户与当前HttpServletRequest关联。例如,以下将以用户名(用户),密码“ password”和角色“ ROLE_USER”的用户身份(不需要存在)运行:

Note

该支持通过将用户与HttpServletRequest关联而起作用。要将请求与SecurityContextHolder关联,您需要确保SecurityContextPersistenceFilterMockMvc实例关联。实现此目的的几种方法是:

  • Invoking apply(springSecurity())

  • 将 Spring Security 的FilterChainProxy添加到MockMvc

  • 使用MockMvcBuilders.standaloneSetup时,手动将SecurityContextPersistenceFilter添加到MockMvc实例可能很有意义

mvc
	.perform(get("/").with(user("user")))

您可以轻松进行自定义。例如,以下用户名(Management 员),用户名“ admin”,密码“ pass”以及角色“ ROLE_USER”和“ ROLE_ADMIN”将作为用户(不需要存在)运行。

mvc
	.perform(get("/admin").with(user("admin").password("pass").roles("USER","ADMIN")))

如果您有要使用的自定义UserDetails,也可以轻松指定。例如,以下代码将使用指定的UserDetails(不需要存在)与UsernamePasswordAuthenticationToken一起运行,该UsernamePasswordAuthenticationToken的主体为指定的UserDetails

mvc
	.perform(get("/").with(user(userDetails)))

您可以使用以下身份以匿名用户身份运行:

mvc
	.perform(get("/").with(anonymous()))

如果您使用默认用户运行,并希望以匿名用户身份执行一些请求,则此功能特别有用。

如果您想要一个自定义的Authentication(不需要存在),可以使用以下方法:

mvc
	.perform(get("/").with(authentication(authentication)))

您甚至可以使用以下方法自定义SecurityContext

mvc
	.perform(get("/").with(securityContext(securityContext)))

通过使用MockMvcBuilders的默认请求,我们还可以确保针对每个请求以特定用户身份运行。例如,以下用户名(Management 员),用户名“ admin”,密码“ password”和角色“ ROLE_ADMIN”将作为用户(不需要存在)运行:

mvc = MockMvcBuilders
		.webAppContextSetup(context)
		.defaultRequest(get("/").with(user("user").roles("ADMIN")))
		.apply(springSecurity())
		.build();

如果发现您在许多测试中使用的是同一用户,建议将用户移至某个方法。例如,您可以在自己的名为CustomSecurityMockMvcRequestPostProcessors的类中指定以下内容:

public static RequestPostProcessor rob() {
	return user("rob").roles("ADMIN");
}

现在,您可以对SecurityMockMvcRequestPostProcessors执行静态导入,并在测试中使用它:

import static sample.CustomSecurityMockMvcRequestPostProcessors.*;

...

mvc
	.perform(get("/").with(rob()))

在带有 Comments 的 Spring MVC 测试中以用户身份运行

作为使用RequestPostProcessor创建用户的替代方法,您可以使用第 11 章,测试方法的安全性中描述的 Comments。例如,以下将对具有用户名“ 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("/"))
		...
}

12.2.4 测试 HTTP 基本身份验证

虽然始终可以使用 HTTP Basic 进行身份验证,但是记住 Headers 名称,格式和对值进行编码有点繁琐。现在,可以使用 Spring Security 的httpBasic RequestPostProcessor完成此操作。例如,以下代码段:

mvc
	.perform(get("/").with(httpBasic("user","password")))

将通过确保在 HTTP 请求中填充以下 Headers,尝试使用 HTTP Basic 对用户名“ user”和密码“ password”进行身份验证:

Authorization: Basic dXNlcjpwYXNzd29yZA==

12.3 SecurityMockMvcRequestBuilders

Spring MVC Test 还提供了一个RequestBuilder接口,可用于创建测试中使用的MockHttpServletRequest。 Spring Security 提供了RequestBuilder的一些实现,可用来简化测试。为了使用 Spring Security 的RequestBuilder实现,请确保使用以下静态导入:

import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestBuilders.*;

12.3.1 测试基于表单的身份验证

您可以使用 Spring Security 的测试支持轻松地创建一个请求,以测试基于表单的身份验证。例如,以下代码将使用用户名“ user”,密码“ password”和有效的 CSRF 令牌向“/login”提交 POST:

mvc
	.perform(formLogin())

定制请求很容易。例如,以下内容将使用用户名“ admin”,密码“ pass”和有效的 CSRF 令牌向“/auth”提交 POST:

mvc
	.perform(formLogin("/auth").user("admin").password("pass"))

我们还可以自定义包含用户名和密码的参数名称。例如,这是上述请求,已修改为包括 HTTP 参数“ u”上的用户名和 HTTP 参数“ p”上的密码。

mvc
	.perform(formLogin("/auth").user("u","admin").password("p","pass"))

12.3.2 测试注销

使用标准 Spring MVC Test 相当简单,但是您可以使用 Spring Security 的测试支持来简化测试注销。例如,以下代码将使用有效的 CSRF 令牌向“/logout”提交 POST:

mvc
	.perform(logout())

您还可以自定义要发布到的 URL。例如,下面的代码片段将使用有效的 CSRF 令牌向“/signout”提交 POST:

mvc
	.perform(logout("/signout"))

12.4 SecurityMockMvcResultMatchers

有时希望对请求做出各种与安全性有关的 assert。为了满足这一需求,Spring Security Test 支持实现了 Spring MVC Test 的ResultMatcher接口。为了使用 Spring Security 的ResultMatcher实现,请确保使用以下静态导入:

import static org.springframework.security.test.web.servlet.response.SecurityMockMvcResultMatchers.*;

12.4.1 未经身份验证的 assert

有时 assert 没有与MockMvc调用的结果相关联的经过身份验证的用户可能很有价值。例如,您可能要测试提交的用户名和密码无效,并验证没有用户通过身份验证。您可以使用 Spring Security 的测试支持轻松地执行以下操作:

mvc
	.perform(formLogin().password("invalid"))
	.andExpect(unauthenticated());

12.4.2 身份验证 assert

通常,我们必须 assert 已通过身份验证的用户存在。例如,我们可能要验证我们已成功验证。我们可以使用以下代码片段来验证基于表单的登录是否成功:

mvc
	.perform(formLogin())
	.andExpect(authenticated());

如果我们想 assert 用户的角色,我们可以优化我们以前的代码,如下所示:

mvc
	.perform(formLogin().user("admin"))
	.andExpect(authenticated().withRoles("USER","ADMIN"));

或者,我们可以验证用户名:

mvc
	.perform(formLogin().user("admin"))
	.andExpect(authenticated().withUsername("admin"));

我们还可以结合以下 assert:

mvc
	.perform(formLogin().user("admin").roles("USER","ADMIN"))
	.andExpect(authenticated().withUsername("admin"));