用于集成测试的 Spring 启动身份验证



我正在尝试为我的控制器运行集成测试,但如果我不进行身份验证,我会遇到问题。这是我的控制器:

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@TestPropertySource(properties = {"security.basic.enabled=false", "management.security.enabled=false"})
@EnableAutoConfiguration(exclude = {org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class})
public class HelloControllerIT {
private final ObjectMapper mapper = new ObjectMapper();
@Autowired private TestRestTemplate template;
@Test
public void test1() throws Exception {
ObjectNode loginRequest = mapper.createObjectNode();
loginRequest.put("username","name");
loginRequest.put("password","password");
JsonNode loginResponse = template.postForObject("/authenticate", loginRequest.toString(), JsonNode.class);
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());
headers.add("Content-Type", "application/json");
return new HttpEntity<>(null, headers);
HttpEntity request = getRequestEntity();
ResponseEntity response = template.exchange("/get",
HttpMethod.GET,
request,
new ParameterizedTypeReference<List<Foo>>() {});
//assert stuff
}
}

当我运行它时,一切正常。但是如果我注释掉这一行:

headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());

我收到错误:

org.springframework.http.converter.HttpMessageNotReadableException: Could not read JSON document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1]; nested exception is com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1]
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:234)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.read(AbstractJackson2HttpMessageConverter.java:219)
at org.springframework.web.client.HttpMessageConverterExtractor.extractData(HttpMessageConverterExtractor.java:95)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:917)
at org.springframework.web.client.RestTemplate$ResponseEntityResponseExtractor.extractData(RestTemplate.java:901)
at org.springframework.web.client.RestTemplate.doExecute(RestTemplate.java:655)
at org.springframework.web.client.RestTemplate.execute(RestTemplate.java:613)
at org.springframework.web.client.RestTemplate.exchange(RestTemplate.java:559)
at org.springframework.boot.test.web.client.TestRestTemplate.exchange(TestRestTemplate.java:812)
at com.test.HelloControllerIT.test1(HelloControllerIT.java:75)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:50)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:47)
at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
at org.junit.internal.runners.statements.RunBefores.evaluate(RunBefores.java:26)
at org.springframework.test.context.junit4.statements.RunBeforeTestMethodCallbacks.evaluate(RunBeforeTestMethodCallbacks.java:75)
at org.springframework.test.context.junit4.statements.RunAfterTestMethodCallbacks.evaluate(RunAfterTestMethodCallbacks.java:86)
at org.springframework.test.context.junit4.statements.SpringRepeat.evaluate(SpringRepeat.java:84)
at org.junit.runners.ParentRunner.runLeaf(ParentRunner.java:325)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:252)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: com.fasterxml.jackson.databind.JsonMappingException: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token
at [Source: java.io.PushbackInputStream@272a5bc6; line: 1, column: 1]
at com.fasterxml.jackson.databind.JsonMappingException.from(JsonMappingException.java:270)
at com.fasterxml.jackson.databind.DeserializationContext.reportMappingException(DeserializationContext.java:1234)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1122)
at com.fasterxml.jackson.databind.DeserializationContext.handleUnexpectedToken(DeserializationContext.java:1075)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.handleNonArray(CollectionDeserializer.java:338)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:269)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:259)
at com.fasterxml.jackson.databind.deser.std.CollectionDeserializer.deserialize(CollectionDeserializer.java:26)
at com.fasterxml.jackson.databind.ObjectMapper._readMapAndClose(ObjectMapper.java:3798)
at com.fasterxml.jackson.databind.ObjectMapper.readValue(ObjectMapper.java:2922)
at org.springframework.http.converter.json.AbstractJackson2HttpMessageConverter.readJavaType(AbstractJackson2HttpMessageConverter.java:231)
... 38 more

显然,顶部的安全注释不起作用。那么问题到底是什么,我该如何解决呢?

编辑1:我尝试过做:

Object response = template.exchange("/get", HttpMethod.GET, request, Object.class);

并得到:

<401 Unauthorized,{status=401, message=Authentication failed, errorCode=10, timestamp=1497654855545},{X-Content-Type-Options=[nosniff], X-XSS-Protection=[1; mode=block], Cache-Control=[no-cache, no-store, max-age=0, must-revalidate], Pragma=[no-cache], Expires=[0], X-Frame-Options=[DENY], Content-Type=[application/json;charset=ISO-8859-1], Content-Length=[89], Date=[Fri, 16 Jun 2017 23:14:15 GMT]}>

为了我们的安全,我们使用org.springframework.security.authentication.AuthenticationProviderorg.springframework.security.authentication.AuthenticationManager

编辑2:根据skadya的建议,我创建了一个新类,如下所示:

@Configuration
public class AnonymousConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity web) throws Exception {
web.antMatcher("**/*").anonymous();
}
}

但是现在当我运行集成测试时,出现以下错误:

java.lang.IllegalStateException: Failed to load ApplicationContext
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:124)
at org.springframework.test.context.support.DefaultTestContext.getApplicationContext(DefaultTestContext.java:83)
at org.springframework.boot.test.autoconfigure.SpringBootDependencyInjectionTestExecutionListener.prepareTestInstance(SpringBootDependencyInjectionTestExecutionListener.java:47)
at org.springframework.test.context.TestContextManager.prepareTestInstance(TestContextManager.java:230)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.createTest(SpringJUnit4ClassRunner.java:228)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner$1.runReflectiveCall(SpringJUnit4ClassRunner.java:287)
at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.methodBlock(SpringJUnit4ClassRunner.java:289)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:247)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.runChild(SpringJUnit4ClassRunner.java:94)
at org.junit.runners.ParentRunner$3.run(ParentRunner.java:290)
at org.junit.runners.ParentRunner$1.schedule(ParentRunner.java:71)
at org.junit.runners.ParentRunner.runChildren(ParentRunner.java:288)
at org.junit.runners.ParentRunner.access$000(ParentRunner.java:58)
at org.junit.runners.ParentRunner$2.evaluate(ParentRunner.java:268)
at org.springframework.test.context.junit4.statements.RunBeforeTestClassCallbacks.evaluate(RunBeforeTestClassCallbacks.java:61)
at org.springframework.test.context.junit4.statements.RunAfterTestClassCallbacks.evaluate(RunAfterTestClassCallbacks.java:70)
at org.junit.runners.ParentRunner.run(ParentRunner.java:363)
at org.springframework.test.context.junit4.SpringJUnit4ClassRunner.run(SpringJUnit4ClassRunner.java:191)
at org.junit.runner.JUnitCore.run(JUnitCore.java:137)
at com.intellij.junit4.JUnit4IdeaTestRunner.startRunnerWithArgs(JUnit4IdeaTestRunner.java:68)
at com.intellij.rt.execution.junit.IdeaTestRunner$Repeater.startRunnerWithArgs(IdeaTestRunner.java:51)
at com.intellij.rt.execution.junit.JUnitStarter.prepareStreamsAndStart(JUnitStarter.java:242)
at com.intellij.rt.execution.junit.JUnitStarter.main(JUnitStarter.java:70)
Caused by: org.springframework.beans.factory.BeanCreationException: Error creating bean with name 'org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration': Injection of autowired dependencies failed; nested exception is java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on config.AnonymousConfig$$EnhancerBySpringCGLIB$$ba18b8d7@6291f725, so it cannot be used on security.WebSecurityConfig$$EnhancerBySpringCGLIB$$9d88e7e@1bfaaae1 too.
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:372)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1264)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:553)
at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:483)
at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:306)
at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230)
at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:302)
at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:197)
at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:761)
at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:866)
at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:542)
at org.springframework.boot.context.embedded.EmbeddedWebApplicationContext.refresh(EmbeddedWebApplicationContext.java:122)
at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:737)
at org.springframework.boot.SpringApplication.refreshContext(SpringApplication.java:370)
at org.springframework.boot.SpringApplication.run(SpringApplication.java:314)
at org.springframework.boot.test.context.SpringBootContextLoader.loadContext(SpringBootContextLoader.java:120)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:98)
at org.springframework.test.context.cache.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:116)
... 23 more
Caused by: java.lang.IllegalStateException: @Order on WebSecurityConfigurers must be unique. Order of 100 was already used on config.AnonymousConfig$$EnhancerBySpringCGLIB$$ba18b8d7@6291f725, so it cannot be used on security.WebSecurityConfig$$EnhancerBySpringCGLIB$$9d88e7e@1bfaaae1 too.
at org.springframework.security.config.annotation.web.configuration.WebSecurityConfiguration.setFilterChainProxySecurityConfigurer(WebSecurityConfiguration.java:148)
at sun.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
at sun.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:62)
at sun.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
at java.lang.reflect.Method.invoke(Method.java:498)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor$AutowiredMethodElement.inject(AutowiredAnnotationBeanPostProcessor.java:701)
at org.springframework.beans.factory.annotation.InjectionMetadata.inject(InjectionMetadata.java:88)
at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:366)
... 40 more

看起来它与我们在正常项目中的网络安全配置冲突。这是该文件:

@EnableWebSecurity
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//configuration
}

我尝试添加@Order(1000)来解决上述问题,但最终仍然401 Unauthorized

您可以尝试排除更多自动配置:

@EnableAutoConfiguration(exclude = {
org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration.class,
org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration.class
})

顺便说一句,排除内容的更优雅方法是在测试源中定义application-test.properties并用@Profile("test")标记测试。然后只需将其添加到您的配置中:

spring.autoconfigure.exclude=org.springframework.boot.autoconfigure.security.SecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.SecurityFilterAutoConfiguration,org.springframework.boot.autoconfigure.security.FallbackWebSecurityAutoConfiguration,org.springframework.boot.autoconfigure.security.oauth2.OAuth2AutoConfiguration

您可以在此处找到所有可以排除的配置: spring.factory

有几个选项可以在 Spring 启动集成测试中提供身份验证。您可能需要调整一些内容才能使其正常工作。

基于模拟的方法

这使用注入 MockMvc 的测试WebApplicationContext@WithMockUser注释来提供身份验证用户,WithMockUserSecurityContextFactory为模拟用户创建安全上下文。

SecurityMockMvcConfigurersMockMvc注册安全筛选器springSecurityFilterChain

import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.http.MediaType;
import org.springframework.security.test.context.support.WithMockUser;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.Matchers.notNullValue;
import static org.springframework.security.test.web.servlet.setup.SecurityMockMvcConfigurers.springSecurity;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.MOCK)
public class HelloControllerIT  {
@Autowired
private WebApplicationContext context;
private  MockMvc mvc;
@Before
public void setup() {
mvc = MockMvcBuilders
.webAppContextSetup(context)
.apply(springSecurity()) // enable security for the mock set up
.build();
}
@WithMockUser(value = "test", password = "pass")
@Test
public void test() throws Exception {
String contentType = MediaType.APPLICATION_JSON + ";charset=UTF-8";
String authzToken = mvc
.perform(
post("/authenticate")
.contentType(
MediaType.APPLICATION_JSON).
content("")).
andExpect(status().isOk())
.andExpect(content().contentType(contentType))
.andExpect(jsonPath("$.token", is(notNullValue())))
.andReturn().getResponse().getContentAsString();
System.out.print(authzToken);//{"token":"1a3434a"}
}
}

基于内存中身份验证提供程序的方法

这将内存中身份验证提供程序与基本身份验证用户一起使用。

注册内存中身份验证提供程序并启用基本身份验证,在WebSecurityConfigurerAdapter中禁用HttpSecurity匿名访问。

注册内存中提供程序后,DefaultInMemoryUserDetailsManagerConfigurer在内存中创建基本身份验证用户。

启用基本身份验证后,HttpBasicConfigurer将配置BasicAuthenticationFilter。对测试用户进行身份验证并创建安全上下文。

安全配置

@EnableWebSecurity
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(AuthenticationManagerBuilder auth) throws Exception {
// register test user with in memory authentication provider 
auth.inMemoryAuthentication().withUser("test").password("pass").roles("ROLES");
}
@Override
public void configure(HttpSecurity http) throws Exception {
// enable basic authentication & disable anoymous access
http.authorizeRequests().anyRequest().authenticated().and().httpBasic().and().anonymous().disable();    
}
}

身份验证终结点

@Controller
@RequestMapping("/authenticate")
public class AuthenticationController {
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public TokenClass getToken() {
TokenClass tokenClass = new TokenClass();
tokenClass.setToken("1a3434a");
return tokenClass;
}
}

波乔

public class TokenClass {
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}

测试控制器

import com.fasterxml.jackson.databind.JsonNode;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.boot.test.context.SpringBootTest;
import org.springframework.boot.test.web.client.TestRestTemplate;
import org.springframework.http.*;
import org.springframework.test.context.junit4.SpringRunner;
import java.util.Arrays;
import java.util.Base64;
@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
public class HelloControllerIT  {
@Autowired
private TestRestTemplate template;
@Test
public void test() throws Exception {
HttpHeaders authHeaders = new HttpHeaders();
String token = new String(Base64.getEncoder().encode(
("test" + ":" + "pass").getBytes()));
authHeaders.set("Authorization", "Basic " + token);
JsonNode loginResponse = template.postForObject("/authenticate", new HttpEntity<>(null, authHeaders), JsonNode.class);
HttpHeaders authzHeaders = new HttpHeaders();
authzHeaders.setAccept(Arrays.asList(MediaType.APPLICATION_JSON));
authzHeaders.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());
authzHeaders.add("Content-Type", "application/json");
ResponseEntity response = template.exchange("/secure",
HttpMethod.GET,
new HttpEntity<>(null, authzHeaders),
String.class
);
}
}

看起来默认安全配置正在启动。除非我看到您是完整的配置,否则很难确认这一点。如果可能的话,你能发布你的最小项目(在github上?)。

由于您不希望在执行集成测试期间强制实施身份验证,因此可以启用对应用程序资源的匿名访问。

要启用匿名访问,您可以在测试源目录下添加以下类。它将在引导 Web 应用程序期间配置匿名访问。(不应看到 401 响应代码)

@Configuration
public class AllowAnonymousWebAccess extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity web) throws Exception {
web.antMatcher("**/*").anonymous();
}
}

长期面临这个问题。终于解决了。您需要模拟授权服务器创建测试配置文件,并且还需要模拟 Spring 安全用户详细信息服务。这是我在博客中找到的代码。

测试授权服务器

@Configuration
@EnableAuthorizationServer
@ActiveProfiles("test")
public class AuthorizationTestServer extends AuthorizationServerConfigurerAdapter {
private AuthenticationManager authenticationManager;

@Autowired
public AuthorizationTestServer(AuthenticationManager authenticationManager) {
this.authenticationManager = authenticationManager;
}
@Override
public void configure(AuthorizationServerSecurityConfigurer oauthServer) throws Exception {
oauthServer.checkTokenAccess("permitAll()");
oauthServer.allowFormAuthenticationForClients();
}
@Override
public void configure(ClientDetailsServiceConfigurer clients) throws Exception {
super.configure(clients);
clients.inMemory()
.withClient("user")
.secret("password")
.authorizedGrantTypes("password")
.scopes("openid");
}
@Override
public void configure(AuthorizationServerEndpointsConfigurer endpoints) throws Exception {
super.configure(endpoints);
endpoints.authenticationManager(this.authenticationManager);
}
}

测试用户详细信息服务

@Service
@ActiveProfiles("test")
public class UserDetailTestService implements UserDetailsService {
@Override
public UserDetails loadUserByUsername(String email) throws UsernameNotFoundException {
return new User("dummyUser","dummyPassword",true,true,
true,true, AuthorityUtils.createAuthorityList("USER"));
}
}

主测试类

@RunWith(SpringRunner.class)
@SpringBootTest(webEnvironment = SpringBootTest.WebEnvironment.RANDOM_PORT)
@ActiveProfiles("test")
@AutoConfigureMockMvc
public class JmStudentServiceApplicationTests {
@Autowired
private WebApplicationContext wac;
@Autowired
private MockMvc mockMvc;

@Autowired
private TestRestTemplate restTemplate;
@Autowired
private StudentRepository studentRepository;
@Test
public void test() throws Exception{
String accessToken = obtainAccessToken("dummyUser", "dummyPassword");
Student student = new Student();
student.setId("2222");
student.setName("test student");
studentRepository.createStudent(student);
assertTrue(studentRepository.getStudentById("2222").getName().equals("test student"));
MvcResult result = mockMvc.perform(get("/students/by-id/2222")
.header("Authorization", "Bearer " + accessToken)
.accept(MediaType.APPLICATION_JSON))
.andExpect(status().isOk())
.andReturn();

String str = result.getResponse().getContentAsString();
assertTrue(str.contains(""id":"2222""));
}
private String obtainAccessToken(String username, String password) throws Exception {
MultiValueMap<String, String> params = new LinkedMultiValueMap<>();
params.add("grant_type", "password");
params.add("username", username);
params.add("password", password);
params.add("scope", "openid");
String base64ClientCredentials = new String(Base64.encodeBase64("user:password".getBytes()));

ResultActions result
= mockMvc.perform(post("/oauth/token")
.params(params)
.header("Authorization","Basic " + base64ClientCredentials)
.accept("application/json;charset=UTF-8"))
.andExpect(status().isOk());
String resultString = result.andReturn().getResponse().getContentAsString();
JacksonJsonParser jsonParser = new JacksonJsonParser();
return jsonParser.parseMap(resultString).get("access_token").toString();
}
}

为了在测试时绕过基本身份验证 使用 @SpringBootTest 和 TestRestTemplate,我们可以将 .withBasicAuth("admin", "password") 链接到模板,见下文

@Test
public void givenAuthRequestOnPrivateService_shouldSucceedWith200() throws Exception {
ResponseEntity<String> result = template.withBasicAuth("spring", "secret")
.getForEntity("/private/hello", String.class);
assertEquals(HttpStatus.OK, result.getStatusCode());
}

有关详细信息,请参阅 https://www.baeldung.com/spring-security-integration-tests

看起来身份验证确实有效,但您以错误的方式处理响应。

下面是下面的代码,您尝试将响应解析为List<Foo>

ResponseEntity response = template.exchange("/get",
HttpMethod.GET,
request,
new ParameterizedTypeReference<List<Foo>>() {}
);

但是由于您没有提供身份验证标头,服务器会响应一些自定义错误(显然包装到 Json Object 中),并且在测试中得到此异常,说它无法从 Json 对象解析ArrayList(以START_OBJECT令牌开头,如{)。

Could not read JSON document: Can not deserialize instance of java.util.ArrayList out of START_OBJECT token

尝试将响应作为 Object 处理,以便您可以看到实际出现的内容。

template.exchange("/get", HttpMethod.GET, request, Object.class);

但这不能作为最终解决方案。我相信您应该根据 Http 响应代码处理响应正文,如果它是 200 - 解析为List<>,否则解析为Map<>或服务器返回的任何结构。

为什么强制实施安全配置

根据 Spring Boot 文档,当您使用@SpringBootTest注释您的类时,并且您没有指定配置替代方案,那么 Spring 会搜索一个@SpringBootApplication类作为您的主要配置。 Spring 在测试类的包中开始搜索,然后搜索包层次结构。 据推测,它正在查找您的主要配置,以及随之而来的一切,包括您不需要的安全配置。

溶液

在 Spring Boot 2.0.3 中验证的最简单解决方案是将@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)更改为@SpringBootApplication(exclude = SecurityAutoConfiguration.class). 当您进行此更改时,Spring 会将您的测试类注册为主配置类,因此会确认您的排除。 或者,创建一个单独的配置类,以便在驻留在基本包中的所有集成测试之间共享,并且所有集成测试都可以找到这些测试。

我想出了如何在没有内存身份验证和/或模拟的情况下做到这一点。

public class TestConf extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.permitAll();
}
}

并且仅在运行测试用例时使用 spring 活动配置文件运行上述配置。

@WebMvcTest用于测试时,初始化排除自动配置参数会禁用 Spring 安全性。

@WebMvcTest(controllers = MyController.class, 
excludeAutoConfiguration = SecurityAutoConfiguration.class)

@SpringBootTest@AutoConfigureMockMvc一起使用时,将addFilters参数设置为false也会禁用 Spring 安全性。

@SpringBootTest(classes = MyController.class)
@AutoConfigureMockMvc(addFilters = false)

简单方法:

SecurityContextHolder.getContext().setAuthentication(new UsernamePasswordAuthenticationToken("username", "password"));

然后,您仍然可以像登录时一样使用主体。

最新更新