web-dev-qa-db-fra.com

Authentification d'amorçage printanière pour les tests d'intégration

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

18
Richard

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

12
Danylo Zatorsky

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
        );
    }
}
3
Veeram

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();
    }
}
2
skadya

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();
   }

}
1
Bablu

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.

1
ikryvorotenko

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.

0
Richard

Pourquoi la configuration de sécurité est-elle appliquée?

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. 

Solution

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.