J'essaie d'exécuter un test d'intégration pour mon contrôleur, mais des problèmes surviennent si je ne m'authentifie pas. Voici mon contrôleur:
@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
}
}
Quand je lance ça, tout fonctionne. Mais si je commente la ligne:
headers.add("X-Authorization", "Bearer " + loginResponse.get("token").textValue());
Je reçois l'erreur:
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
De toute évidence, les annotations de sécurité en haut ne fonctionnent pas. Alors, quel est exactement le problème et comment puis-je résoudre ce problème?
Edit 1: J'ai essayé de faire:
Object response = template.exchange("/get", HttpMethod.GET, request, Object.class);
Et j'ai:
<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]}>
Pour notre sécurité, nous utilisons org.springframework.security.authentication.AuthenticationProvider
et org.springframework.security.authentication.AuthenticationManager
Edit 2: Selon la suggestion de skadya, j'ai créé une nouvelle classe comme celle-ci:
@Configuration
public class AnonymousConfig extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity web) throws Exception {
web.antMatcher("**/*").anonymous();
}
}
Mais maintenant, lorsque je lance mon test d'intégration, l'erreur suivante apparaît:
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
On dirait que cela entre en conflit avec la configuration Websecurity que nous avons dans le projet normal. Voici ce fichier:
@EnableWebSecurity
@EnableWebMvc
@EnableGlobalMethodSecurity(prePostEnabled = true)
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
//configuration
}
J'ai essayé d'ajouter @Order(1000)
qui corrigeait le problème ci-dessus mais restait dans un 401 Unauthorized
Vous pouvez essayer d’exclure quelques configurations automatiques supplémentaires:
@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
})
En fait, une manière plus élégante d'exclure des choses est de définir application-test.properties
dans vos sources de test et de marquer votre test avec @Profile("test")
. Ensuite, ajoutez ceci à votre configuration:
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
Vous trouverez ici toutes les configurations possibles pouvant être exclues: spring.factories
Vous avez plusieurs options pour fournir une authentification dans le test d'intégration de démarrage printanier. Vous devrez peut-être ajuster certaines choses pour que tout fonctionne de votre côté.
Approche fictive
Ceci utilise le test WebApplicationContext
injecté dans MockMvc avec @WithMockUser annotation pour fournir l'utilisateur d'authentification et WithMockUserSecurityContextFactory
créant le contexte de sécurité pour l'utilisateur fictif.
SecurityMockMvcConfigurers
enregistre le filtre de sécurité springSecurityFilterChain
avec MockMvc
.
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"}
}
}
Approche basée sur le fournisseur d'autorisation en mémoire
Ceci utilise le fournisseur d'authentification en mémoire avec l'utilisateur d'authentification de base.
Enregistrez le fournisseur d'authentification en mémoire et activez l'authentification de base, désactivez l'accès anonyme dans HttpSecurity
dans la WebSecurityConfigurerAdapter
.
Lorsque le fournisseur en mémoire est enregistré, DefaultInMemoryUserDetailsManagerConfigurer
crée l'utilisateur d'authentification de base dans la mémoire.
Lorsque l'authentification de base est activée, HttpBasicConfigurer
configure BasicAuthenticationFilter
. Authentifie l'utilisateur test et crée le contexte de sécurité.
Configuration de la sécurité
@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();
}
}
Point de terminaison d'authentification
@Controller
@RequestMapping("/authenticate")
public class AuthenticationController {
@RequestMapping(method = RequestMethod.POST)
@ResponseBody
public TokenClass getToken() {
TokenClass tokenClass = new TokenClass();
tokenClass.setToken("1a3434a");
return tokenClass;
}
}
Pojo
public class TokenClass {
private String token;
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
}
Contrôleur de test
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
);
}
}
Il semble que la configuration de sécurité par défaut soit mise en œuvre. À moins que je ne voie votre configuration complète, il est difficile de le confirmer. Si possible, pourriez-vous poster votre projet minimal (sur github?).
Comme vous ne souhaitez pas appliquer l'authentification lors de l'exécution de tests d'intégration, vous pouvez activer l'accès anonyme à vos ressources d'application.
Pour activer l'accès anonyme, vous pouvez ajouter la classe ci-dessous dans le répertoire test source . Il configurera l'accès anonyme lors du démarrage de l'application Web. (ne devrait pas voir le code de réponse 401)
@Configuration
public class AllowAnonymousWebAccess extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity web) throws Exception {
web.antMatcher("**/*").anonymous();
}
}
A été confronté à ce problème pendant une longue période. Enfin résolu le problème. Vous devez simuler le serveur d'autorisations en créant un profil de test et également créer un service de détails des utilisateurs Mock Spring Security. Voici le code que j'ai trouvé dans un blog .
Test du serveur d'autorisation
@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);
}
}
Test User Details Service
@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"));
}
}
Classe de test principale
@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();
}
}
Il semble que l'authentification fonctionne, mais vous ne gérez pas correctement la réponse.
Voici le code ci-dessous, où vous essayez d'analyser la réponse en tant que List<Foo>
ResponseEntity response = template.exchange("/get",
HttpMethod.GET,
request,
new ParameterizedTypeReference<List<Foo>>() {}
);
Mais puisque vous n'avez pas fourni d'en-tête d'authentification, le serveur répond avec une erreur personnalisée (évidemment encapsulé dans Json Object) et vous obtenez cette exception dans le test indiquant qu'il ne peut pas analyser ArrayList
à partir de Json Object (qui commence par START_OBJECT
, comme {
).
Could not read JSON document: Can not deserialize instance of Java.util.ArrayList out of START_OBJECT token
Essayez de gérer la réponse en tant qu'objet afin de voir ce qui s'y passe réellement.
template.exchange("/get", HttpMethod.GET, request, Object.class);
Mais cela ne fonctionnera pas comme une solution finale. Je crois que vous devez gérer le corps de la réponse en fonction du code de réponse Http , si c'est 200 - parser comme List<>
, sinon parser comme Map<>
ou quelle que soit la structure retournée par le serveur.
J'ai compris comment faire cela sans authentification et/ou moquage en mémoire.
public class TestConf extends WebSecurityConfigurerAdapter {
@Override
public void configure(HttpSecurity http) throws Exception {
http.csrf()
.disable()
.authorizeRequests()
.anyRequest()
.permitAll();
}
}
Et utilisez les profils actifs Spring pour exécuter la configuration ci-dessus uniquement lors de l'exécution de scénarios de test.
Selon Spring Boot documentation , lorsque vous annotez votre classe avec @SpringBootTest
et que vous n'avez pas spécifié d'alternative de configuration, utilisez Spring avec la recherche d'une classe @SpringBootApplication
qui servira de configuration principale. Spring commence la recherche dans le package de votre classe de test, puis effectue une recherche dans le package hiearchy. Vraisemblablement, il recherche votre configuration principale et tout ce que cela implique, y compris votre configuration de sécurité indésirable.
La solution la plus simple, vérifiée dans Spring Boot 2.0.3, consiste à remplacer @EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)
par @SpringBootApplication(exclude = SecurityAutoConfiguration.class)
. Lorsque vous apportez cette modification, Spring enregistrera votre classe de test en tant que classe de configuration principale et accusera donc votre exclusion. Vous pouvez également créer une classe de configuration distincte à partager entre tous vos tests d'intégration, qui réside dans un package de base et sera trouvée par tous vos tests d'intégration.