使用Spring Boot禁用单元测试的安全性

93

我正在尝试创建一个带有安全机制的简单Spring Boot Web项目。我可以顺利启动应用程序,安全功能也正常工作。但是,我有一些组件想要在没有安全保护(或根本不测试——我无法让测试起作用)的情况下进行测试。

我遇到了一个异常,指示它找不到ObjectPostProcessor对象,因此无法启动容器。

Caused by: org.springframework.beans.factory.NoSuchBeanDefinitionException: 找不到所需类型 [org.springframework.security.config.annotation.ObjectPostProcessor] 的限定bean。

14:01:50.937 [main] ERROR o.s.boot.SpringApplication - 应用启动失败 org.springframework.beans.factory.BeanCreationException: 创建 'fmpdfApplication.ApplicationSecurity' bean 失败,自动注入依赖项失败。 嵌套异常是 org.springframework.beans.factory.BeanCreationException: 无法自动装配方法:public void org.springframework.security.config.annotation.web.configuration.WebSecurityConfigurerAdapter.setObjectPostProcessor(org.springframework.security.config.annotation.ObjectPostProcessor); 嵌套异常是 org.springframework.beans.factory.NoSuchBeanDefinitionException: 没有符合类型[org.springframework.security.config.annotation.ObjectPostProcessor]的bean被找到。期望至少1个bean作为这个依赖关系的自动装配候选。依赖注解为:{} at org.springframework.beans.factory.annotation.AutowiredAnnotationBeanPostProcessor.postProcessPropertyValues(AutowiredAnnotationBeanPostProcessor.java:334) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.populateBean(AbstractAutowireCapableBeanFactory.java:1210) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.doCreateBean(AbstractAutowireCapableBeanFactory.java:537) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractAutowireCapableBeanFactory.createBean(AbstractAutowireCapableBeanFactory.java:476) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory$1.getObject(AbstractBeanFactory.java:303) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.DefaultSingletonBeanRegistry.getSingleton(DefaultSingletonBeanRegistry.java:230) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.doGetBean(AbstractBeanFactory.java:299) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.AbstractBeanFactory.getBean(AbstractBeanFactory.java:194) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.beans.factory.support.DefaultListableBeanFactory.preInstantiateSingletons(DefaultListableBeanFactory.java:755) ~[spring-beans-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.context.support.AbstractApplicationContext.finishBeanFactoryInitialization(AbstractApplicationContext.java:757) ~[spring-context-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.context.support.AbstractApplicationContext.refresh(AbstractApplicationContext.java:480) ~[spring-context-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.boot.SpringApplication.refresh(SpringApplication.java:686) ~[spring-boot-1.2.4.RELEASE.jar:1.2.4.RELEASE] at org.springframework.boot.SpringApplication.run(SpringApplication.java:320) ~[spring-boot-1.2.4.RELEASE.jar:1.2.4.RELEASE] at org.springframework.boot.test.SpringApplicationContextLoader.loadContext(SpringApplicationContextLoader.java:103) ~[spring-boot-1.2.4.RELEASE.jar:1.2.4.RELEASE] at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContextInternal(DefaultCacheAwareContextLoaderDelegate.java:68) ~[spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.test.context.DefaultCacheAwareContextLoaderDelegate.loadContext(DefaultCacheAwareContextLoaderDelegate.java:86) ~[spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.test.context.DefaultTestContext.getApplicationContext(DefaultTestContext.java:72) ~[spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.injectDependencies(DependencyInjectionTestExecutionListener.java:117) ~[spring-test-4.1.6.RELEASE.jar:4.1.6.RELEASE] at org.springframework.test.context.support.DependencyInjectionTestExecutionListener.prepareTestInstance(DependencyInjectionTestExecutionListener.java:83) ~[spring
@RunWith(SpringJUnit4ClassRunner)
@SpringApplicationConfiguration(classes = FmpdfApplication)
@ActiveProfiles(["test", "mockstore"])
class PdfUpdaterTest {

    @Resource PdfUpdater pdfUpdater
    ...

我的(相关的)gradle依赖项如下:

compile("org.springframework.boot:spring-boot-starter-actuator")
compile("org.springframework.boot:spring-boot-starter-security")
compile("org.springframework.boot:spring-boot-starter-web")
compile("org.springframework.boot:spring-boot-starter-jdbc")
testCompile("org.springframework.boot:spring-boot-starter-test")

我尝试过设置 management.security.enabled=false security.basic.enabled=false 但这没有帮助

另一个相关的信息是:我需要自定义安全性,所以我按照以下模式进行操作:

@Configuration
@Order(SecurityProperties.ACCESS_OVERRIDE_ORDER)
protected static class ApplicationSecurity extends WebSecurityConfigurerAdapter {
        @Override
        public void configure(AuthenticationManagerBuilder auth) throws Exception {
    ..

这是问题的一部分吗?如果相关,有没有办法使其@Lazy?

更新:如果我将单元测试标记为@WebIntegrationTest,则一切正常——但它会启动一个嵌入式Tomcat服务器。如何在单元测试非Web内容时禁用Spring Security?


FmpdfApplication是模拟/测试还是真实的应用程序类? - ikumen
如果您需要禁用安全性,则这不是单元测试,而是集成测试。 - Justinas Jakavonis
2
我认为在单元测试和集成测试的定义上过于苛求是没有多大价值的。我更倾向于关注风险缓解、成本、耦合度、以及未来变化的可扩展性等方面的平衡,而不是教条主义。但既然这个小评论串已经被提到了门口:我认为Justas搞错了。安全基础设施是一个多组件的东西(你的身份验证系统、授权系统);集成测试是覆盖多个组件的事情,而单元测试则专注于单个组件(通常会模拟其他依赖组件)。 - steve_ash
15个回答

65

更新答案:

我最近了解到,如果我正在使用MockMvc和AutoConfigureMockMvc测试我的控制器,我可以在其上设置secure=false来禁用适用于您的控制器的任何安全性。

@AutoConfigureMockMvc(secure = false)

如果不使用基本身份验证,请按以下方式操作。


你得到的异常与我得到的异常非常不同,但是如果你想在运行测试用例时禁用安全性,可以尝试使用配置文件并在测试配置文件中使用属性来禁用基本安全性。这就是我所做的 -

  1. WebSecurityConfigurerAdapter的实现添加注释@Profile(value = {"development", "production"}) -
    @Configuration
    @EnableWebSecurity
    @Profile(value = {"development", "production"})
    public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

2. 现在,在test/resources目录下,创建application-test.yml文件来定义测试配置属性,并添加以下内容 -

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

3. 现在,针对您的测试用例,请添加此注释以应用活动配置文件@ActiveProfiles(value = "test")。这是我的类的样子 -

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

我这样做禁用了我的测试用例的安全性,因此我能够访问经过身份验证的 URL。希望对你也有用。祝你好运!!!


2
被接受的答案对我没用,我正在使用OAuth2并在资源服务器上执行此操作,因此将@Profile注释添加到ResourceServerConfigurerAdapter中,但其余部分相同,并且完美地工作。 - 3urdoch
27
@AutoConfigureMockMvc(secure = false)现已被弃用。您可以将其替换为@AutoConfigureMockMvc。 - Gabriel Bauman
9
没有属性- secure - andrew17
13
你可以选择 addFilters = false - twobiers

59
在我的情况下,@AutoConfigureMockMvc(addFilters = false) 对我很有帮助。
示例:
@WebMvcTest(MyController.class)
@AutoConfigureMockMvc(addFilters = false)
@TestPropertySource(locations="classpath:application-test.properties")
public class MyControllerTests {

干杯


1
正是我正在寻找的! 曾经在每个测试之前手动设置MockMvc对象使用Mockito,现在改用这些注释来加载更多设置Web MVC的配置bean,以便生产应用程序中会影响控制器行为(例如jackson ObjectMapper配置)而无需额外手动设置这些内容。 - GameSalutes
2
使用这种方法,我得到了“无法加载ApplicationContext”的错误。 - vinicius gati

55

FmpdfApplication 很可能已经使用了 @EnableAutoConfiguration 注解(或者使用 @SpringBootApplication 注解,它是元注解 @EnableAutoConfiguration 的一个组合注解),这将会通过自动配置启用并配置 Spring Security。

如果你想查看正在被自动配置的内容,可以启动 Web 应用并访问 autoconfig 端点(例如:http://localhost:8080/autoconfig)。然后搜索 "Security",以查看检测到哪些 "AutoConfiguration" 类。

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

@EnableAutoConfiguration(exclude = { SecurityAutoConfiguration.class, ManagementSecurityAutoConfiguration.class })
当然,你不会希望在生产部署中排除它们。因此,你需要为生产和测试分别创建一个独立的@Configuration类。
问候,
Sam
附:你可能也会发现我对以下问题的回答有用:Spring-Boot模块化集成测试

2
请注意,在 src/test/java 中的 @SpringBootApplication 注释类中,@EnableAutoConfiguration(exclude = {SecurityAutoconfiguration.class, ManagementWebSecurityAutoConfiguration.class}) 将会被使用。同时请注意,我正在使用 spring boot 1.3.0 版本,并且使用了 ManagementWebSecurityAutoConfiguration.class。 - Nico de Wet
3
FYI,我认为/autoconfig只在您将执行器引入pom文件时才起作用。由于我没有它,我在src/test/resources/application.properties中添加了“logging.level.org.springframework=DEBUG”以查看自动配置带来的报告。 - bmauter
对于SpringBoot 2.7.x,它必须是“ManagementWebSecurityAutoConfiguration.class”,请参见“Web”部分。 - Stefan

11

尝试使用此方法来禁用Spring Boot安全性以进行测试


@AutoConfigureMockMvc(secure = false)

@RunWith(SpringRunner.class)
@WebMvcTest(SampleController.class)
@AutoConfigureMockMvc(secure = false)
public class SampleControllerIntegrationTest {

1
我还需要加上 @ActiveProfiles("test") 才能使它工作。 - Clauds
10
这已经被弃用了。 - Gabriel Bauman
4
根据您的使用场景,您可以改用@AutoConfigureMockMvc(addFilters = false) - W.K.S

10

4

针对我来说,我更新了我的测试注释以解决此问题。现在已经可以正常运行。

来源:

@RunWith(SpringRunner.class)
@SpringBootTest
@AutoConfigureMockMvc

To:

@RunWith(SpringRunner.class)
@SpringBootTest
@WebAppConfiguration
@ActiveProfiles(value = "test")
@AutoConfigureMockMvc(addFilters = false)

3
如果您的应用程序不是基于Web的,但需要spring-security jar作为依赖项,并且在测试时不想使用spring-boot的自动配置来进行spring-security,则可以添加以下内容:
@SpringBootTest(webEnvironment = WebEnvironment.NONE)

在你的测试类中。


2
您可以在测试类中添加以下注释: @AutoConfigureMockMvc(addFilters = false)

1

在我找到这个解决方案之前,一切都对我无效:

@Bean
@Profile("integration-test")
public WebSecurityCustomizer webSecurityCustomizer() {
    return (web) -> web.ignoring()
            .antMatchers("/**");
}

在测试中使用配置文件"integration-test"。


1

这是我在Spring Boot 2.7.x中使用的解决方案。

这种方法使用了配置文件机制。

当使用测试配置文件时,它将禁用所有调用的安全性。

@Bean
@Profile("test")
public WebSecurityCustomizer webSecurityCustomizer() {
 //Since we added the Spring Security to pom.xml and the spring security default
 //Behavior is ... well to secure and block all traffic
 //This will disable the behavior when testing none secured related tests
 return web -> web.ignoring().anyRequest();
}

在我的情况下,我还需要将@Profile("!test")添加到SecurityFilterChain bean工厂中。 - AscendingEagle

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接