4.2.2 测试 Web 安全
Spring Security 能让你非常方便地测试安全加固后的 Web 应用程序。为了利用这点优势,你必须在项目里添加 Spring Security 的测试模块。要在 Gradle 里做到这一点,你需要的就是以下 testCompile
依赖:
testCompile("org.springframework.security:spring-security-test")
如果你用的是 Maven,则添加以下 <dependency>
:
<dependency>
<groupId>org.springframework.security</groupId>
<artifactId>spring-security-test</artifactId>
<scope>test</scope>
</dependency>
应用程序的 Classpath 里有了 Spring Security 的测试模块之后,只需在创建 MockMvc
实例时运用 Spring Security 的配置器。
@Before
public void setupMockMvc() {
mockMvc = MockMvcBuilders
.webAppContextSetup(webContext)
.apply(springSecurity())
.build();
}
springSecurity()
方法返回了一个 Mock MVC 配置器,为 Mock MVC 开启了 Spring Security 支持。只需像上面这样运用就行了,Spring Security 会介入 MockMvc
上执行的每个请求。具体的安全配置取决于你如何配置 Spring Security(或者 Spring Boot 如何自动配置 Spring Security)。在阅读列表这个应用程序里,我们在第 3 章里创建 SecurityConfig.java
时,配置也是如此。
springSecurity()
方法 springSecurity()
是 SecurityMockMvcConfigurers
的一个静态方法,考虑到可读性,我已经将其静态导入。
开启了 Spring Security 之后,在请求主页的时候,我们便不能只期待 HTTP 200 响应。如果请求未经身份验证,我们应该期待重定向到登录页面:
@Test
public void homePage_unauthenticatedUser() throws Exception {
mockMvc.perform(get("/"))
.andExpect(status().is3xxRedirection())
.andExpect(header().string("Location",
"http://localhost/login"));
}
不过,经过身份验证的请求又该如何发起呢?Spring Security 提供了两个注解。
@WithMockUser
:加载安全上下文,其中包含一个UserDetails
,使用了给定的用户名、密码和授权。@WithUserDetails
:根据给定的用户名查找UserDetails
对象,加载安全上下文。
在这两种情况下,Spring Security 的安全上下文都会加载一个 UserDetails
对象,添加了该注解的测试方法在运行过程中都会使用该对象。 @WithMockUser
注解是两者里比较基础的那个,允许显式声明一个 UserDetails
,并加载到安全上下文。
@Test
@WithMockUser(username="craig",
password="password",
roles="READER")
public void homePage_authenticatedUser() throws Exception {
...
}
如你所见, @WithMockUser
绕过了对 UserDetails
对象的正常查询,用给定的值创建了一个 UserDetails
对象取而代之。在简单的测试里,这就够用了。但我们的测试需要 Reader
(实现了 UserDetails
)而非 @WithMockUser
创建的通用 UserDetails
。为此,我们需要 @WithUserDetails
。
@WithUserDetails
注解使用事先配置好的 UserDetailsService
来加载 UserDetails
对象。回想一下第 3 章,我们配置了一个 UserDetailsService
Bean,它会根据给定的用户名查找并返回一个 Reader
对象。太完美了!所以我们要为测试方法添加 @WithUserDetails
注解,如代码清单 4-4 所示。
代码清单 4-4 测试带有用户身份验证的安全加固方法
@Test
@WithUserDetails("craig") ←---使用 craig 用户
public void homePage_authenticatedUser() throws Exception {
Reader expectedReader = new Reader(); ←---配置期望的 Reader
expectedReader.setUsername("craig");
expectedReader.setPassword("password");
expectedReader.setFullname("Craig Walls");
mockMvc.perform(get("/")) ←---发起 GET 请求
.andExpect(status().isOk())
.andExpect(view().name("readingList"))
.andExpect(model().attribute("reader",
samePropertyValuesAs(expectedReader)))
.andExpect(model().attribute("books", hasSize(0)))
}
在代码清单 4-4 里,我们通过 @WithUserDetails
注解声明要在测试方法执行过程中向安全上下文里加载 craig 用户。 Reader
会放入模型,该测试方法先创建了一个期望的 Reader
对象,后续可以用来进行比较。随后 GET
请求发起,也有了针对视图名和模型内容的断言,其中包括名为 reader
的模型属性。
同样,此处没有启动 Servlet 容器来运行这些测试,Spring 的 Mock MVC 取代了实际的 Servlet 容器。这样做的好处是测试方法运行相对较快。因为不需要等待服务器启动,而且不需要打开 Web 浏览器发送表单,所以测试比较简单快捷。
不过,这并不是一个完整的测试。它比直接调用控制器方法要好,但它并没有真的在 Web 浏览器里执行应用程序,验证呈现出的视图。为此,我们需要启动一个真正的 Web 服务器,用真实浏览器来访问它。让我们来看看 Spring Boot 如何启动一个真实的 Web 服务器来帮助测试。
绑定邮箱获取回复消息
由于您还没有绑定你的真实邮箱,如果其他用户或者作者回复了您的评论,将不能在第一时间通知您!
发布评论