使用 Spring 安全测试来测试安全的 Spring MVC 控制器
Using Spring security test to test a secured Spring MVC controller
遵循 documentation 关于使用 Spring 安全测试 为连接在 [=40 后面的 spring MVC 应用程序编写测试=]Spring安全.
这是一个香草 spring-boot 应用程序,采用典型的 spring-security wiring。这是主要的 Application.java
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
这是 spring-安全的接线:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/","/sign_up").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf()
.and()
.exceptionHandling()
.accessDeniedPage("/access_denied")
.and()
.logout()
.permitAll();
}
}
如您所见,除了“/”和“/sign_up”之外的所有请求都需要进行身份验证。我已经通过部署应用程序验证身份验证方案工作正常。
现在是有趣的部分:编写 spring mvc 测试。我提供的 link 提供了一些编写此类测试的好方法,其中 spring-security-test 框架允许插入模拟 users/security-contexts。我采取了
的方法
- 定义模拟 UserDetails 接口,并且
- 使用 SecurityContextFactory 创建一个 SecurityContext,
- 应在测试启动期间插入到应用程序上下文中。
1.的代码如下:
@WithSecurityContext(factory=WithMockUserDetailsSecurityContextFactory.class)
public @interface WithMockUserDetails {
String firstName() default "apil";
String lastName() default "tamang";
String password() default "test";
long id() default 999;
String email() default "apil@test.com";
}
2.的代码如下:
final class WithMockUserDetailsSecurityContextFactory
implements WithSecurityContextFactory<WithMockUserDetails>{
@Override
public SecurityContext createSecurityContext(WithMockUserDetails mockUserDetails) {
/*
* Use an anonymous implementation for 'UserDetails' to return a
* mock authentication object, which is then set to the SecurityContext
* for the test runs.
*/
UserDetails principal=new UserDetails() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//another anonmyous interface implementation.
GrantedAuthority auth=new GrantedAuthority() {
@Override
public String getAuthority() {
return "ROLE_USER";
}
};
List<GrantedAuthority> authorities=new ArrayList<>();
authorities.add(auth);
return authorities;
}
@Override
public String getPassword() {
return mockUserDetails.password();
}
@Override
public String getUsername() {
return mockUserDetails.email();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
};
Authentication authentication=new
UsernamePasswordAuthenticationToken(principal,principal.getPassword(),principal.getAuthorities());
SecurityContext context= SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
最后,下面是测试class:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebAppConfiguration
public class UserControllerTest {
@Autowired
private WebApplicationContext context;
@Autowired
private Filter springSecurityFilterChain;
private MockMvc mvc;
@Before
public void setup(){
mvc= MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
public void testRootIsOk() throws Exception {
mvc.perform(get("/"))
.andExpect(status().isOk());
}
@Test
public void expectRedirectToLogin() throws Exception {
mvc.perform(get("/testConnect"))
.andExpect(redirectedUrl("http://localhost/login"));
}
@Test
@WithMockUserDetails
public void testAuthenticationOkay() throws Exception {
mvc.perform(get("/testConnect"))
.andExpect(content().string("good request."));
}
}
测试输出运行:
- 测试 1 通过。
- 测试 2 次通过。
- 测试 3 失败。预期输出但得到 <>。
测试 3 很可能失败,因为 'SecurityContext' 从未得到适当填充。根据文档,它应该有效。不知道我错过了什么。非常感谢任何帮助。
您需要添加
@Retention(RetentionPolicy.RUNTIME)
添加到您的自定义注释,以便在运行时保留注释信息。否则 WithSecurityContextTestExecutionListener
无法检测到您的注释。
遵循 documentation 关于使用 Spring 安全测试 为连接在 [=40 后面的 spring MVC 应用程序编写测试=]Spring安全.
这是一个香草 spring-boot 应用程序,采用典型的 spring-security wiring。这是主要的 Application.java
@SpringBootApplication
public class Application {
private static final Logger log = LoggerFactory.getLogger(Application.class);
public static void main(String[] args) {
SpringApplication.run(Application.class);
}
}
这是 spring-安全的接线:
@Configuration
@EnableWebSecurity
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Autowired
@Qualifier("customUserDetailsService")
UserDetailsService userDetailsService;
@Autowired
public void configureGlobalSecurity(AuthenticationManagerBuilder auth) throws Exception {
auth.userDetailsService(userDetailsService);
}
@Override
protected void configure(HttpSecurity http) throws Exception {
http.authorizeRequests()
.antMatchers("/","/sign_up").permitAll()
.anyRequest().authenticated()
.and()
.formLogin()
.and()
.csrf()
.and()
.exceptionHandling()
.accessDeniedPage("/access_denied")
.and()
.logout()
.permitAll();
}
}
如您所见,除了“/”和“/sign_up”之外的所有请求都需要进行身份验证。我已经通过部署应用程序验证身份验证方案工作正常。
现在是有趣的部分:编写 spring mvc 测试。我提供的 link 提供了一些编写此类测试的好方法,其中 spring-security-test 框架允许插入模拟 users/security-contexts。我采取了
的方法- 定义模拟 UserDetails 接口,并且
- 使用 SecurityContextFactory 创建一个 SecurityContext,
- 应在测试启动期间插入到应用程序上下文中。
1.的代码如下:
@WithSecurityContext(factory=WithMockUserDetailsSecurityContextFactory.class)
public @interface WithMockUserDetails {
String firstName() default "apil";
String lastName() default "tamang";
String password() default "test";
long id() default 999;
String email() default "apil@test.com";
}
2.的代码如下:
final class WithMockUserDetailsSecurityContextFactory
implements WithSecurityContextFactory<WithMockUserDetails>{
@Override
public SecurityContext createSecurityContext(WithMockUserDetails mockUserDetails) {
/*
* Use an anonymous implementation for 'UserDetails' to return a
* mock authentication object, which is then set to the SecurityContext
* for the test runs.
*/
UserDetails principal=new UserDetails() {
@Override
public Collection<? extends GrantedAuthority> getAuthorities() {
//another anonmyous interface implementation.
GrantedAuthority auth=new GrantedAuthority() {
@Override
public String getAuthority() {
return "ROLE_USER";
}
};
List<GrantedAuthority> authorities=new ArrayList<>();
authorities.add(auth);
return authorities;
}
@Override
public String getPassword() {
return mockUserDetails.password();
}
@Override
public String getUsername() {
return mockUserDetails.email();
}
@Override
public boolean isAccountNonExpired() {
return true;
}
@Override
public boolean isAccountNonLocked() {
return true;
}
@Override
public boolean isCredentialsNonExpired() {
return true;
}
@Override
public boolean isEnabled() {
return true;
}
};
Authentication authentication=new
UsernamePasswordAuthenticationToken(principal,principal.getPassword(),principal.getAuthorities());
SecurityContext context= SecurityContextHolder.createEmptyContext();
context.setAuthentication(authentication);
return context;
}
}
最后,下面是测试class:
@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = {Application.class})
@WebAppConfiguration
public class UserControllerTest {
@Autowired
private WebApplicationContext context;
@Autowired
private Filter springSecurityFilterChain;
private MockMvc mvc;
@Before
public void setup(){
mvc= MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity())
.build();
}
@Test
public void testRootIsOk() throws Exception {
mvc.perform(get("/"))
.andExpect(status().isOk());
}
@Test
public void expectRedirectToLogin() throws Exception {
mvc.perform(get("/testConnect"))
.andExpect(redirectedUrl("http://localhost/login"));
}
@Test
@WithMockUserDetails
public void testAuthenticationOkay() throws Exception {
mvc.perform(get("/testConnect"))
.andExpect(content().string("good request."));
}
}
测试输出运行:
- 测试 1 通过。
- 测试 2 次通过。
- 测试 3 失败。预期输出但得到 <>。
测试 3 很可能失败,因为 'SecurityContext' 从未得到适当填充。根据文档,它应该有效。不知道我错过了什么。非常感谢任何帮助。
您需要添加
@Retention(RetentionPolicy.RUNTIME)
添加到您的自定义注释,以便在运行时保留注释信息。否则 WithSecurityContextTestExecutionListener
无法检测到您的注释。