web-dev-qa-db-ja.com

Spring Boot、テストのセキュリティを無効にする

Spring Bootバージョン「1.3.0.M5」を使用しています(バージョン「1.2.5.RELEASE」も試しました)。春のセキュリティを追加しました:

<dependency>
  <groupId>org.springframework.boot</groupId>
  <artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
  <groupId>org.springframework.security</groupId>
  <artifactId>spring-security-test</artifactId>
  <scope>test</scope>
</dependency>

およびコード:

@SpringBootApplication
public class SpringBootMainApplication {
  public static void main(String[] args) {
    SpringApplication.run(SpringBootMainApplication.class, args);
  }
}

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {
  @Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.inMemoryAuthentication().withUser("user").password("password").roles("USER");
  }
  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.authorizeRequests()
    .antMatchers("/api/sampleentity").authenticated()
    .and().authorizeRequests()
    .and().formLogin().permitAll()
    .and().logout().permitAll().logoutUrl("/logout")
    .logoutSuccessUrl("/");
  }
  @Override
  @Bean
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }
}

@RestController
@RequestMapping("/api/sampleentity")
public class SampleEntityController {
  @RequestMapping(method= RequestMethod.GET)
  public Iterable<SampleEntity> getAll() {
    return ImmutableSet.of();
  }
  @RequestMapping(method=RequestMethod.POST)
  @ResponseStatus(value= HttpStatus.CREATED)
  public SampleEntity create(@RequestBody SampleEntity sampleEntity) {
    return sampleEntity;
  }
}

/ api/sampleentityがアクセスしているときに失敗するテスト:org.springframework.web.client.HttpClientErrorException:403 Forbidden(...)

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootMainApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0"})
public class SampleEntityTest {
  @Value("${local.server.port}")
  private int port;
  private String url;
  private RestTemplate restTemplate;
  @Autowired
  private ApplicationContext context;
  @BeforeClass
  public static void authenticate(){
//ONE TRY
//        Authentication authentication =
//                new UsernamePasswordAuthenticationToken("user", "password",
//                                                        AuthorityUtils.createAuthorityList("USER")); //tried "ROLE_USER"
//        SecurityContextHolder.getContext().setAuthentication(authentication);
  }
  @Before
  public void setUp() {
    url = String.format("http://localhost:%s/api/sampleentity", port);
    restTemplate = new RestTemplate();
//ANOTHER TRY
//        AuthenticationManager authenticationManager = context.getBean(AuthenticationManager.class);
//        Authentication authentication = authenticationManager
//                .authenticate(new UsernamePasswordAuthenticationToken("user", "password", AuthorityUtils.createAuthorityList("USER"))); //tried "ROLE_USER"
//        SecurityContextHolder.getContext().setAuthentication(authentication);
  }
  //THIS METHOD SHOULD WORK !
  @Test
//ANOTHER TRY
//@WithMockUser(username="user",password = "password", roles={"USER"})//tried "ROLE_USER"
  public void testEntity_create() throws Exception {
    SampleEntity sampleEntity = create("name", 1);
    ResponseEntity<SampleEntity> response = restTemplate.postForEntity(url, sampleEntity, SampleEntity.class);
    assertEquals(HttpStatus.CREATED, response.getStatusCode());
  }
  private SampleEntity create(String name, int id) {
    SampleEntity entity = new SampleEntity();
    entity.setName(name);
    entity.setId(id);
    return entity;
  }
}

Main()からアプリケーションを実行してURLにアクセスすると: http:// localhost:8080/api/sampleentity ログインページにリダイレクトされます。

テストを実行してセキュリティを無効にしたり、ユーザーにログインしたりするにはどうすればよいですか?

-私の解決策:プロファイルを使用してテストからセキュリティを除外します:

@SpringBootApplication
@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class})
public class SpringBootMainApplication {body the same}

@EnableWebSecurity
@Import(SecurityAutoConfiguration.class)
@Profile("!test")
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {body the same}

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootMainApplication.class)
@WebAppConfiguration
@IntegrationTest({"server.port=0"})
@ActiveProfiles("test")
public class SampleEntityTest {body the same}
10
Hollow.Quincy

問題を解決するには、構成とテストにいくつかの変更を加える必要があります。

まず、ソリューションが機能しない理由を説明します。

  1. Spring RestTemplateクラスは、RESTサービスにアクセスするための可能な方法ですが、構築方法にいくつかのヘッダー情報がありません(これは、RestTemplate)。そのため、認証は機能しませんでした。
  2. RestTemplateリクエストが新しいセッションを作成する可能性があるため、RestTemplateクラスを使用しているため、最初のソリューションの試みは機能しません。それは完全に異なる環境を設定します。私のコードは、@PreAuthorizeアノテーションで保護されたメソッドをテストする場合に機能しますが、そのようなメソッドをテストで直接実行する必要があり、有効な認証が必要な場合に限ります。
  3. 現在のSpringセキュリティ構成では、ユーザーを自動的に承認することはできません。

次に、コードに必要な変更を次に示します。

最初に構成クラス

@Configuration
@EnableWebSecurity
public class WebSecurityConfiguration extends WebSecurityConfigurerAdapter {

@Override
  protected void configure(AuthenticationManagerBuilder auth) throws Exception {
  auth.inMemoryAuthentication().withUser("user").password("password").roles("USER" );
  }

  @Override
  protected void configure(HttpSecurity http) throws Exception {
    http.httpBasic().and().csrf().disable()
    .authorizeRequests().antMatchers("/api/sampleentity").authenticated()
    .and().authorizeRequests().antMatchers("/users").hasRole("ADMIN")
    .and().formLogin().permitAll()
    .and().logout().permitAll().logoutUrl("/logout")
    .logoutSuccessUrl("/");
  }

  @Override
  @Bean
  public AuthenticationManager authenticationManagerBean() throws Exception {
    return super.authenticationManagerBean();
  }
}

HttpBasic認証サポートを追加する必要があり(httpヘッダー属性を介した認証を有効にするため)、csrfトークンを無効にしました(後者は便宜上、アプリケーションの重要度に応じて再度有効にする必要があります)。

そして2番目のテストクラス:

import Java.io.IOException;
import Java.nio.charset.Charset;
import Java.util.Arrays;

import javax.servlet.Filter;

import org.junit.Assert;
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.IntegrationTest;
import org.springframework.boot.test.SpringApplicationConfiguration;
import org.springframework.http.MediaType;
import org.springframework.http.converter.HttpMessageConverter;
import org.springframework.http.converter.json.MappingJackson2HttpMessageConverter;
import org.springframework.mock.http.MockHttpOutputMessage;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import org.springframework.test.context.web.WebAppConfiguration;
import org.springframework.test.web.servlet.MockMvc;
import org.springframework.test.web.servlet.setup.MockMvcBuilders;
import org.springframework.web.context.WebApplicationContext;

import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get;
import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.post;
import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.*;
import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.*;

@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = SpringBootMainApplication.class)
@WebAppConfiguration
@IntegrationTest({ "server.port=0" })
public class SampleEntityTest {

private String url;
private MockMvc mockMvc;
private HttpMessageConverter mappingJackson2HttpMessageConverter;

private MediaType contentType = new MediaType(
        MediaType.APPLICATION_JSON.getType(),
        MediaType.APPLICATION_JSON.getSubtype(), Charset.forName("utf8"));

@Autowired
private WebApplicationContext webApplicationContext;

@Autowired
private Filter springSecurityFilterChain;

@Autowired
void setConverters(HttpMessageConverter<?>[] converters) {
    for (HttpMessageConverter hmc : Arrays.asList(converters)) {
        if (hmc instanceof MappingJackson2HttpMessageConverter) {
            this.mappingJackson2HttpMessageConverter = hmc;
        }
    }

    Assert.assertNotNull("the JSON message converter must not be null",
            this.mappingJackson2HttpMessageConverter);
}

@Before
public void setUp() {
    url = "/api/sampleentity";
    mockMvc = MockMvcBuilders.webAppContextSetup(webApplicationContext)
            .addFilters(springSecurityFilterChain).build();
}

@Test
public void testEntityGet() throws Exception {
    mockMvc.perform(
            get(url)
            .with(httpBasic("user", "password")))
            .andExpect(status().isOk());
}

@Test
public void testEntityPost() throws Exception {
    SampleEntity sampleEntity = new SampleEntity();
    sampleEntity.setName("name");
    sampleEntity.setId(1);
    String json = json(sampleEntity);
    mockMvc.perform(
            post(url)
            .contentType(contentType)
            .content(json)
            .with(httpBasic("user", "password")))
            .andExpect(status().isCreated());
}

protected String json(Object o) throws IOException {
    MockHttpOutputMessage mockHttpOutputMessage = new MockHttpOutputMessage();
    this.mappingJackson2HttpMessageConverter.write(o,
            MediaType.APPLICATION_JSON, mockHttpOutputMessage);
    return mockHttpOutputMessage.getBodyAsString();
}

}

ここでは、スプリング/スプリングセキュリティテストアプローチを使用しました。

使用バージョン:

    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>1.2.5.RELEASE</version>
    </parent>

    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-core</artifactId>
        <version>4.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-web</artifactId>
        <version>4.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-config</artifactId>
        <version>4.0.2.RELEASE</version>
    </dependency>
    <dependency>
        <groupId>org.springframework.security</groupId>
        <artifactId>spring-security-test</artifactId>
        <version>4.0.2.RELEASE</version>
        <scope>test</scope>
    </dependency>

Rest APIをテストしたい場合は、Chrome用のPostmanプラグインをお勧めします。それはあなたがはるかに速く問題を特定するのを助けることができるので。

これが最終的に問題を解決するのに役立つことを願っています。

2
BeWu

自動構成されているものを確認する場合は、Webアプリを起動し、自動構成エンドポイントにアクセスします(例: http:// localhost:8080/autoconfig )。次に、「Security」を検索して、どの「AutoConfiguration」クラスが検出されているかを確認します。

次に、次のようなクラスを除外することで、セキュリティの自動構成を無効にできます。

_@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class })
_

もちろん、実稼働環境でそれらを除外することは望ましくありません。したがって、本番環境とテスト用に個別の_@Configuration_クラスが必要になります。

または詳細な回答が必要な場合は、以下の手順を実行してください


アノテーション@Profile(value = {"development", "production"})WebSecurityConfigurerAdapterの実装に追加します

_@Configuration
    @EnableWebSecurity
    @Profile(value = {"development", "production"})
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {
_

ここで、test/resourcesで、application-test.ymlを作成して、テストプロファイルのプロパティを定義し、これを追加します-

_# Security enable/disable
security:
  basic:
    enabled: false
_

ここで、テストケースにこのアノテーションを追加して、アクティブプロファイル@ActiveProfiles(value = "test")を適用します。これが私のクラスの様子です-

_@RunWith(SpringJUnit4ClassRunner.class)
@SpringApplicationConfiguration(classes = Application.class)
@WebAppConfiguration
@ActiveProfiles(value = "test")
@IntegrationTest({"server.port=0"})
public class SampleControllerIntegrationTest {
_

これを行うと、テストのセキュリティが無効になります。頑張ってください!!!

0
Kunal Vohra