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
关联,您需要确保SecurityContextPersistenceFilter
与MockMvc
实例关联。实现此目的的几种方法是:
-
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"));