Spring OAuth 和启动集成测试
Spring OAuth and Boot Integration Test
运行 Spring 对 OAuth 资源服务器配置的 Web 应用程序再次启动集成测试的最佳方法是什么。
我可以想到两种理论方法:
- 在不实际调用授权服务器的情况下模拟资源服务器中的安全上下文。
- 将授权服务器作为测试的一部分嵌入并将身份验证重定向到它。
我想知道其他人是如何解决这个问题的。
我使用 spring security 4.x @WithSecurityContext('user')
注释创建模拟 SecurityContext
并 'user'
登录。然后调用我的 REST API 使用 MockMvc
我检索 SecurityContext
并将其附加到呼叫中。
像这样:
@Test
@Transactional
@WithSecurityContext('user')
public void getAllParcels() throws Exception {
// Initialize the database
long size = parcelRepository.count();
parcelRepository.saveAndFlush(parcel);
// Get all the parcels
restParcelMockMvc.perform(get("/api/parcels").with(security()))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[" + size + "].id").value(parcel.getId()))
.andExpect(jsonPath("$.[" + size + "].lot").value(DEFAULT_LOT))
.andExpect(jsonPath("$.[" + size + "].localName").value(DEFAULT_LOCAL_NAME));
}
其中 security()
是静态方法:
public static RequestPostProcessor security() {
return SecurityMockMvcRequestPostProcessors.securityContext(SecurityContextHolder.getContext());
}
因此,为我的测试方法创建了使用 @WithSecurityContext('user')
模拟 SecurityContext
和登录 'user'
的经过身份验证的用户。然后在该方法中,我检索此模拟 SecurityContext
并将其附加到 REST API 调用,以使我的 oAuth 认为用户已完成身份验证。这基本上是您在问题中建议的第一种方法。
为此,您必须将 OAuth 切换为有状态才能进行测试。不然不行。
像这样:
@Configuration
public class OAuth2ServerConfiguration {
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired(required = false)
@Qualifier("oauth2StatelessSecurityContext")
private Boolean stateless = Boolean.TRUE; // STATEFUL switching for tests!
@Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
@Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/logs/**").hasAnyAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(stateless);
super.configure(resources);
}
}
...
你看到我的 stateless
属性 只在测试中被注入。在正常 运行 中,它使用它的默认值 true
(因此它是无状态的)。对于测试,我声明了 oauth2StatelessSecurityContext
值为 false
的 Bean,因此它变成有状态的以进行测试。
我为测试定义了这个配置:
@Configuration
public class OAuth2Statefull {
@Bean
@Primary
public Boolean oauth2StatelessSecurityContext() {
return Boolean.FALSE;
}
}
我就是这样做的。我希望我的解释是可以理解的。
这个答案与 Ondrej 提供的答案非常相似,但要简单得多。
Spring Security 4 提供测试支持。要使用它,请确保您拥有 spring-security-test-4.0.2.RELEASE.jar(或您的类路径中的更新版本)。您还需要确保您使用的是 spring-test-4.1.0.RELEASE(或更新版本)。
接下来您可以使用 MockMvc,如其他答案所示。但是,如果您 setup MockMvc 使用以下内容:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class OAuthTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
// ADD THIS!!
.apply(springSecurity())
.build();
}
这就是原因
- 您不再需要担心运行是否处于无状态模式
- 这也意味着您不需要按照其他答案中的说明使用 apply(springSecurity())。
简而言之,您应该可以这样做:
@Test
@WithSecurityContext('user')
public void performOAuth() throws Exception {
...
// No need for apply(security())!!
restParcelMockMvc.perform(get("/api/some-resource"))
.andExpect(...);
}
我建议您通读参考资料的其余部分 Spring Security Testing section,因为它提供了许多其他详细信息,包括如何使用自定义身份验证。
运行 Spring 对 OAuth 资源服务器配置的 Web 应用程序再次启动集成测试的最佳方法是什么。
我可以想到两种理论方法:
- 在不实际调用授权服务器的情况下模拟资源服务器中的安全上下文。
- 将授权服务器作为测试的一部分嵌入并将身份验证重定向到它。
我想知道其他人是如何解决这个问题的。
我使用 spring security 4.x @WithSecurityContext('user')
注释创建模拟 SecurityContext
并 'user'
登录。然后调用我的 REST API 使用 MockMvc
我检索 SecurityContext
并将其附加到呼叫中。
像这样:
@Test
@Transactional
@WithSecurityContext('user')
public void getAllParcels() throws Exception {
// Initialize the database
long size = parcelRepository.count();
parcelRepository.saveAndFlush(parcel);
// Get all the parcels
restParcelMockMvc.perform(get("/api/parcels").with(security()))
.andExpect(status().isOk())
.andExpect(content().contentType(MediaType.APPLICATION_JSON))
.andExpect(jsonPath("$.[" + size + "].id").value(parcel.getId()))
.andExpect(jsonPath("$.[" + size + "].lot").value(DEFAULT_LOT))
.andExpect(jsonPath("$.[" + size + "].localName").value(DEFAULT_LOCAL_NAME));
}
其中 security()
是静态方法:
public static RequestPostProcessor security() {
return SecurityMockMvcRequestPostProcessors.securityContext(SecurityContextHolder.getContext());
}
因此,为我的测试方法创建了使用 @WithSecurityContext('user')
模拟 SecurityContext
和登录 'user'
的经过身份验证的用户。然后在该方法中,我检索此模拟 SecurityContext
并将其附加到 REST API 调用,以使我的 oAuth 认为用户已完成身份验证。这基本上是您在问题中建议的第一种方法。
为此,您必须将 OAuth 切换为有状态才能进行测试。不然不行。
像这样:
@Configuration
public class OAuth2ServerConfiguration {
@Configuration
@EnableResourceServer
protected static class ResourceServerConfiguration extends ResourceServerConfigurerAdapter {
@Autowired(required = false)
@Qualifier("oauth2StatelessSecurityContext")
private Boolean stateless = Boolean.TRUE; // STATEFUL switching for tests!
@Inject
private Http401UnauthorizedEntryPoint authenticationEntryPoint;
@Inject
private AjaxLogoutSuccessHandler ajaxLogoutSuccessHandler;
@Override
public void configure(HttpSecurity http) throws Exception {
http
.exceptionHandling()
.authenticationEntryPoint(authenticationEntryPoint)
.and()
.logout()
.logoutUrl("/api/logout")
.logoutSuccessHandler(ajaxLogoutSuccessHandler)
.and()
.csrf()
.requireCsrfProtectionMatcher(new AntPathRequestMatcher("/oauth/authorize"))
.disable()
.headers()
.frameOptions().disable().and()
.sessionManagement()
.sessionCreationPolicy(SessionCreationPolicy.STATELESS)
.and()
.authorizeRequests()
.antMatchers("/api/authenticate").permitAll()
.antMatchers("/api/register").permitAll()
.antMatchers("/api/logs/**").hasAnyAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api/**").authenticated()
.antMatchers("/metrics/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/health/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/dump/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/shutdown/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/beans/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/configprops/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/info/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/autoconfig/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/env/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/trace/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/api-docs/**").hasAuthority(AuthoritiesConstants.ADMIN)
.antMatchers("/protected/**").authenticated();
}
@Override
public void configure(ResourceServerSecurityConfigurer resources) throws Exception {
resources.stateless(stateless);
super.configure(resources);
}
}
...
你看到我的 stateless
属性 只在测试中被注入。在正常 运行 中,它使用它的默认值 true
(因此它是无状态的)。对于测试,我声明了 oauth2StatelessSecurityContext
值为 false
的 Bean,因此它变成有状态的以进行测试。
我为测试定义了这个配置:
@Configuration
public class OAuth2Statefull {
@Bean
@Primary
public Boolean oauth2StatelessSecurityContext() {
return Boolean.FALSE;
}
}
我就是这样做的。我希望我的解释是可以理解的。
这个答案与 Ondrej 提供的答案非常相似,但要简单得多。
Spring Security 4 提供测试支持。要使用它,请确保您拥有 spring-security-test-4.0.2.RELEASE.jar(或您的类路径中的更新版本)。您还需要确保您使用的是 spring-test-4.1.0.RELEASE(或更新版本)。
接下来您可以使用 MockMvc,如其他答案所示。但是,如果您 setup MockMvc 使用以下内容:
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration
@WebAppConfiguration
public class OAuthTests {
@Autowired
private WebApplicationContext context;
private MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
// ADD THIS!!
.apply(springSecurity())
.build();
}
这就是原因
- 您不再需要担心运行是否处于无状态模式
- 这也意味着您不需要按照其他答案中的说明使用 apply(springSecurity())。
简而言之,您应该可以这样做:
@Test
@WithSecurityContext('user')
public void performOAuth() throws Exception {
...
// No need for apply(security())!!
restParcelMockMvc.perform(get("/api/some-resource"))
.andExpect(...);
}
我建议您通读参考资料的其余部分 Spring Security Testing section,因为它提供了许多其他详细信息,包括如何使用自定义身份验证。