弹簧启动,禁用测试的安全性

我使用弹簧启动版本“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 是 access: 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}

答案 1

您必须对配置和测试进行一些更改才能解决问题。

首先,我将解释为什么您的解决方案不起作用:

  1. Spring 类是访问 REST 服务的一种可能方式,但在构造它的方式上缺少一些标头信息(这并不意味着使用 )。这就是身份验证不起作用的原因。RestTemplateRestTemplate
  2. 由于类的使用,我的第一次解决方案尝试不起作用,因为请求可能会创建一个新会话。它设置了一个完全不同的环境。如果要测试使用注释保护的方法,但仅当要直接在测试中执行此类方法并且需要有效的身份验证时,我的代码才能正常工作。RestTemplateRestTemplate@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 令牌(后者只是为了方便起见,您应该根据应用程序的严重性重新启用它们)。

其次是测试类:

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>

如果你想测试你的休息API,我可以推荐你Chrome的Postman插件。因为这可以帮助您更快地识别问题。

我希望这有助于您最终解决您的问题。


答案 2

如果要查看要自动配置的内容,请启动 Web 应用并访问自动配置终结点(例如,http://localhost:8080/autoconfig)。然后搜索“”以查看正在检测哪些“”类。SecurityAutoConfiguration

然后,您可以通过排除这些类来禁用安全性的自动配置,如下所示:

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

这样做将禁用测试的安全性。祝你好运!!!


推荐