本节描述 Spring Security 提供的测试支持。
本节演示如何使用 Spring Security 的测试支持来测试基于方法的安全性。我们首先介绍一个 public class HelloMessageService implements MessageService { @PreAuthorize("authenticated") public String getMessage() { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); return "Hello " + authentication; } }
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 在使用 Spring Security 测试支持之前,我们必须执行一些设置。下面可以看到一个例子: @RunWith(SpringJUnit4ClassRunner.class) 这是如何设置 Spring Security 测试的一个基本示例。亮点是:
记住,我们在 @Test(expected = AuthenticationCredentialsNotFoundException.class) public void getMessageUnauthenticated() { messageService.getMessage(); }
问题是我们如何最容易地将测试作为一个特定的用户来运行?答案是使用 @Test @WithMockUser public void getMessageWithMockUser() { String message = messageService.getMessage(); ... } 具体地说,以下是正确的:
我们的例子很好,因为我们能够利用很多默认值。如果我们想用不同的用户名运行测试怎么办?下面的测试将使用用户名 "customUser" 运行。同样,用户不需要实际存在。 @Test @WithMockUser("customUsername") public void getMessageWithMockUserCustomUsername() { String message = messageService.getMessage(); ... } 我们也可以轻松定制角色。例如,这个测试将用用户名 "admin" 和角色 "ROLE_USER" 和 "ROLE_ADMIN" 来调用。 @Test @WithMockUser(username="admin",roles={"USER","ADMIN"}) public void getMessageWithMockUserCustomUser() { String message = messageService.getMessage(); ... } 如果我们不希望用 ROLE_ 自动前缀值,我们可以利用权限属性。例如,这个测试将用用户名 "admin" 和权限 "USER" 和 "ADMIN" 来调用。 @Test @WithMockUser(username = "admin", authorities = { "ADMIN", "USER" }) public void getMessageWithMockUserCustomAuthorities() { String message = messageService.getMessage(); ... } 当然,把注解放在每一种测试方法上都有点乏味。相反,我们可以将注解放置在类级别,并且每个测试都将使用指定的用户。例如,下面将使用用户名 "admin"、密码 "password" 以及角色 "ROLE_USER" 和 "ROLE_ADMIN" 运行每个测试。 @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration @WithMockUser(username="admin",roles={"USER","ADMIN"}) public class WithMockUserTests {
默认情况下,在 @WithMockUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
使用 @RunWith(SpringJUnit4ClassRunner.class) @WithMockUser public class WithUserClassLevelAuthenticationTests { @Test public void withMockUser1() { } @Test public void withMockUser2() { } @Test @WithAnonymousUser public void anonymous() throws Exception { // override default to run as anonymous user } }
默认情况下,在 @WithAnonymousUser(setupBefore = TestExecutionEvent.TEST_EXECUTION)
虽然
自定义主体通常由返回实现
假设我们有一个 @Test @WithUserDetails public void getMessageWithUserDetails() { String message = messageService.getMessage(); ... }
我们还可以自定义用于从我们的 @Test @WithUserDetails("customUsername") public void getMessageWithUserDetailsCustomUsername() { String message = messageService.getMessage(); ... }
我们还可以提供一个显式 bean 名称来查找 @Test @WithUserDetails(value="customUsername", userDetailsServiceBeanName="myUserDetailsService") public void getMessageWithUserDetailsServiceBeanName() { String message = messageService.getMessage(); ... }
像
默认情况下,在 @WithUserDetails(setupBefore = TestExecutionEvent.TEST_EXECUTION)
我们已经看到,如果我们不使用自定义
我们可以创建自己的注解,它使用 @Retention(RetentionPolicy.RUNTIME) @WithSecurityContext(factory = WithMockCustomUserSecurityContextFactory.class) public @interface WithMockCustomUser { String username() default "rob"; String name() default "Rob Winch"; }
可以看到, public class WithMockCustomUserSecurityContextFactory implements WithSecurityContextFactory<WithMockCustomUser> { @Override public SecurityContext createSecurityContext(WithMockCustomUser customUser) { SecurityContext context = SecurityContextHolder.createEmptyContext(); CustomUserDetails principal = new CustomUserDetails(customUser.name(), customUser.username()); Authentication auth = new UsernamePasswordAuthenticationToken(principal, "password", principal.getAuthorities()); context.setAuthentication(auth); return context; } }
我们现在可以使用新的注解对测试类或测试方法进行注解,Spring Security 的
在创建自己的 final class WithUserDetailsSecurityContextFactory implements WithSecurityContextFactory<WithUserDetails> { private UserDetailsService userDetailsService; @Autowired public WithUserDetailsSecurityContextFactory(UserDetailsService userDetailsService) { this.userDetailsService = userDetailsService; } public SecurityContext createSecurityContext(WithUserDetails withUser) { String username = withUser.value(); Assert.hasLength(username, "value() must be non-empty String"); UserDetails principal = userDetailsService.loadUserByUsername(username); Authentication authentication = new UsernamePasswordAuthenticationToken(principal, principal.getPassword(), principal.getAuthorities()); SecurityContext context = SecurityContextHolder.createEmptyContext(); context.setAuthentication(authentication); return context; } }
默认情况下,在 @WithSecurityContext(setupBefore = TestExecutionEvent.TEST_EXECUTION)
如果经常在测试中重用同一用户,则需要重复指定属性是不理想的。例如,如果有许多与管理用户相关的测试,用户名 "admin" 和角色 @WithMockUser(username="admin",roles={"USER","ADMIN"})
我们可以使用元注解,而不是到处重复。例如,我们可以创建一个名为 @Retention(RetentionPolicy.RUNTIME) @WithMockUser(value="rob",roles="ADMIN") public @interface WithMockAdmin { }
现在,我们可以和更冗长的
元注解与上面描述的任何测试注解一起工作。例如,这意味着我们也可以为 |