web-dev-qa-db-ja.com

統合テストのスプリングブート認証

コントローラーの統合テストを実行しようとしていますが、認証しないと問題が発生します。これが私のコントローラーです。

_@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
_

明らかに、上部のセキュリティアノテーションは機能していません。それでは、問題は正確に何であり、どうすれば修正できますか?

Edit 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.AuthenticationProvider__org.springframework.security.authentication.AuthenticationManager_ を使用しています

編集2:スカディアの提案に従って、私は次のような新しいクラスを作成しました:

_@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
_

通常のプロジェクトにあるwebsecurityの設定と衝突しているようです。そのファイルは次のとおりです。

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

上記の問題を修正した@Order(1000)を追加しようとしましたが、それでも_401 Unauthorized_になりました

22
Richard

さらにいくつかの自動構成を除外してみることができます。

_@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.factories

13
Danylo Zatorsky

スプリングブート統合テストで認証を提供するオプションがいくつかあります。すべてを最後まで機能させるには、いくつかの調整が必要になる場合があります。

モックベースのアプローチ

これは、 MockMvc@ WithMockUser アノテーションに挿入されたテストWebApplicationContextを使用して認証ユーザーを提供し、WithMockUserSecurityContextFactoryが模擬ユーザーのセキュリティコンテキストを作成します。

SecurityMockMvcConfigurersは、セキュリティフィルターspringSecurityFilterChainMockMvcに登録します。

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"}

    }

}

メモリ内認証プロバイダーベースのアプローチ

これは、メモリ内認証プロバイダーと基本認証ユーザーを使用します。

インメモリ認証プロバイダーを登録し、基本認証を有効にし、HttpSecurityWebSecurityConfigurerAdapterで匿名アクセスを無効にします。

メモリ内プロバイダーが登録されると、DefaultInMemoryUserDetailsManagerConfigurerはメモリ内に基本認証ユーザーを作成します。

基本認証が有効な場合、HttpBasicConfigurerBasicAuthenticationFilterを構成します。テストユーザーを認証し、セキュリティコンテキストを作成します。

セキュリティ構成

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

デフォルトのセキュリティ設定が導入されているようです。完全な構成が表示されない限り、これを確認するのは困難です。可能であれば、最小限のプロジェクトを(githubに)投稿してください。

統合テストの実行中に認証を強制したくないので、アプリケーションリソースの匿名アクセスを有効にすることができます。

匿名アクセスを有効にするには、テストソースディレクトリの下にクラスを追加します。 Webアプリケーションのブートストラップ中に匿名アクセスを構成します。 (401応答コードは表示されません)

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

この問題に長い間直面していました。最後に解決しました。テストプロファイルを作成する承認サーバーをモックする必要があり、Spring Securityユーザー詳細サービスをモックする必要もあります。 blog で見つけたコードを次に示します。

テスト認証サーバー

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

}
1
Bablu

認証は機能しているように見えますが、間違った方法で応答を処理しています。

応答をList<Foo>として解析しようとしているコードは次のとおりです。

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

ただし、認証ヘッダーを提供していないため、サーバーはカスタムエラー(明らかにJsonオブジェクトにラップされています)で応答し、Jsonオブジェクト(開始するArrayListを解析できないというテストでこの例外を受け取ります。 START_OBJECTトークンのような{)。

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

応答をオブジェクトとして処理して、実際にそこにあるものを確認できるようにしてください。

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

しかし、これは最終的な解決策としては機能しません。 Http応答コードに基づいて応答本文を処理する必要があります、200の場合はList<>として解析、それ以外の場合はMap<>またはサーバーが返す構造として解析します。

1
ikryvorotenko

インメモリ認証やモックなしでこれを行う方法を見つけました。

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

また、スプリングアクティブプロファイルを使用して、テストケースの実行時にのみ上記の構成を実行します。

0
Richard

セキュリティ構成が実施される理由

Spring Boot documentation によると、クラスに_@SpringBootTest_注釈を付け、構成の代替を指定していない場合、Springは_@SpringBootApplication_クラスを検索して提供しますプライマリ設定として。 Springは、テストクラスのパッケージで検索を開始し、パッケージ階層を検索します。おそらく、それはあなたの主要な設定を見つけており、これがあなたの望まないセキュリティ設定を含めてそれをもたらすすべてです。

溶液

Spring Boot 2.0.3で検証された最も簡単な解決策は、@EnableAutoConfiguration(exclude = SecurityAutoConfiguration.class)@SpringBootApplication(exclude = SecurityAutoConfiguration.class)に変更することです。この変更を行うと、Springはテストクラスをプライマリ構成クラスとして登録するため、除外を承認します。または、個別の構成クラスを作成して、基本パッケージに存在し、すべての統合テストで検出されるすべての統合テストで共有します。