本节演示如何使用Spring安全性的测试支持来测试基于方法的安全性。我们首先介绍一个MessageService,它要求用户通过身份验证才能访问它。
public class HelloMessageService implements MessageService {
@PreAuthorize("authenticated")
public String getMessage() {
Authentication authentication = SecurityContextHolder.getContext().getAuthentication();
return "Hello " + authentication;
}
}
getMessage的结果是一个字符串,对当前的Spring安全认证说“你好”。输出示例如下所示。
Hello
org.springframework.security.authentication.UsernamePasswordAuthenticationToken@ca25360:
Principal: org.springframework.security.core.userdetails.User@36ebcb:
Username: user;
Password: [PROTECTED];
Enabled: true; AccountNonExpired: true;
credentialsNonExpired: true;
AccountNonLocked: true;
Granted Authorities: ROLE_USER;
Credentials: [PROTECTED];
Authenticated: true;
Details: null;
Granted Authorities: ROLE_USER
11.1 Security Test Setup(安全测试设置)
在使用Spring安全测试支持之前,我们必须执行一些设置。下面是一个例子:
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
public class WithMockUserTests {
这是如何设置spring安全测试的一个基本示例。亮点是:
@RunWith指示spring-test模块应该创建一个应用上下文。这与使用现有的spring-test支持没有什么不同。有关更多信息,请参考 [Spring Reference](https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard)
@ContextConfiguration指示spring-test用于创建应用程序上下文的配置。由于未指定配置,将尝试默认配置位置。这与使用现有的spring-test支持没有什么不同。有关更多信息,请参考 [Spring Reference](https://docs.spring.io/spring-framework/docs/4.0.x/spring-framework-reference/htmlsingle/#integration-testing-annotations-standard)
Spring安全性使用WithSecurityContextTestExecutionListener连接到Spring Test支持,这将确保我们的Spring Test由正确的用户运行。它通过在运行测试之前填充SecurityContextHolder来实现这一点。测试完成后,它将清除SecurityContextHolder.如果您只需要Spring安全相关的支持,您可以用@ SecurityTestExecutionListeners替换@ContextConfiguration。
请记住,我们在我们的HelloMessageService中添加了@PreAuthorize注释,因此它需要一个经过身份验证的用户来调用它。
如果我们运行下面的测试,我们希望下面的测试能够通过:
@Test(expected = AuthenticationCredentialsNotFoundException.class)
public void getMessageUnauthenticated() {
messageService.getMessage();
}
11.2 @WithMockUser(使用模拟用户)
11.3 @WithAnonymousUser(使用匿名用户)
使用@ WithAnonymousUser允许以匿名用户身份运行。当您希望用特定用户运行大多数测试,但希望以匿名用户身份运行一小部分测试时,这一点尤其方便。
例如,以下将使用@WithMockUser和匿名用户作为匿名用户与MockUser1和MockUser2一起运行。
11.4 @WithUserDetails(用户详细信息)
虽然@WithMockUser是一种非常方便的入门方式,但它可能并不适用所有情况。例如,应用程序通常希望身份验证主体是特定类型的。这样做是为了使应用程序可以将主体作为自定义类型引用,并减少Spring Security上的耦合。
自定义主体通常由自定义用户详细信息服务返回,该服务返回实现用户详细信息和自定义类型的对象。对于这种情况,使用自定义的用户详细信息服务创建测试用户是很有用的。这正是@WithUserDetails所做的。
假设我们有一个作为bean公开的UserDetailservice,下面的测试将使用类型为UserNamePasswordAuthenticationToken的身份验证和从UserDetailservice返回的用户名为“user”的主体来调用。
我们还可以定制用于从我们的UserDetailsService中查找用户的用户名。例如,该测试将使用用户名为“自定义用户名”的UserDetailsService 返回的主体来执行。
我们还可以提供一个显式的bean名称来查找UserDetailsService。例如,该测试将使用用户名为“myUserDetailsService”的bean来查找用户名“customUsername”。
像@WithMockUser一样,我们也可以在类级别放置我们的注释,以便每个测试使用相同的用户。但是与@WithMockUser不同的是,@WithUserDetails要求用户存在。
11.5 @WithSecurityContext(使用安全上下文)
我们已经看到,如果我们不使用自定义身份验证主体,那么@WithMockUser是一个很好的选择。接下来,我们发现@WithUserDetails将允许我们使用自定义的UserDetailsService来创建我们的身份验证主体,但要求用户存在。我们现在将看到一个允许最大灵活性的选项。
我们可以创建自己的注释,使用@WithSecurityContext来创建我们想要的任何SecurityContext(安全上下文)。例如,我们可以创建一个名为@WithMockCustomUser的注释,如下所示:
您可以看到@WithMockCustomUser用@WithSecurityContext注释进行了注释。这是向Spring安全测试支持发出的信号,表明我们打算为测试创建一个安全上下文。@WithSecurityContext注释要求我们指定一个安全上下文工厂,该工厂将根据我们的@ WithMockCustomUser注释创建一个新的安全上下文。您可以在下面找到我们的WithMockCustomUserSecurityContextFactory实现:
我们现在可以用新的注释来注释测试类或测试方法,而Spring Security的WithSecurityContextTestExecutionListener将确保我们的SecurityContext被适当地填充。
当使用安全上下文工厂实现创建您自己的实现时,很高兴知道它们可以用标准的Spring注释进行注释。例如,WithUserDetailsSecurityContextFactory使用@Autowired注释来获取用户详细信息服务:
11.6 Test Meta Annotations(测试元注释)
如果您经常在测试中重用同一个用户,那么重复指定属性是不理想的。例如,如果有许多与用户名为“admin”且角色为ROLE_USER和ROLE_ADMIN的管理用户相关的测试,您必须编写:
@WithMockUser(username="admin",roles={"USER","ADMIN"})
我们可以使用元注释,而不是到处重复。例如,我们可以创建一个名为WithMockAdmin的元注释:
@Retention(RetentionPolicy.RUNTIME)
@WithMockUser(value="rob",roles="ADMIN")
public@interfaceWithMockAdmin { }
现在我们可以像使用更详细的@WithMockUser一样使用@WithMockAdmin。
元注释与上面描述的任何测试注释一起工作。例如,这意味着我们也可以为@ WithUserDetails(“admin”)创建一个元注释。