【问题标题】:Spring Security HttpSecurity Configuration TestingSpring Security HttpSecurity 配置测试
【发布时间】:2017-04-27 16:50:03
【问题描述】:

我有一个 Spring Boot + Spring Security 应用程序,它有几个antMatchers 路径;一些fullyAuthenticated(),一些permitAll()

我如何编写一个测试来验证SecurityConfiguration 下的我的端点在/api/**(以及最终其他)下是否得到正确保护?

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

    protected void configure(HttpSecurity http) throws Exception {
        http
            //...
            .antMatchers("/api/**").fullyAuthenticated()
    }

}

使用spring-boot-1.5.2.RELEASEspring-security-core-4.2.2-release

澄清1:我想尽可能直接地测试SecurityConfiguration,而不是通过/api/** 端点之一进行传递测试,这些端点可能有自己的@PreAuthorize安全。

澄清2:我想要类似WebSecurityConfigurerAdapterTests的东西。

澄清3:我想在 Spring Security 层 @Autowire 进行测试,最好是 HttpSecurity

【问题讨论】:

  • @dur:理想情况下是 Unit,但集成会很好。我想测试我的配置,而不是我的端点(我有 MockMvc 测试来测试我的端点)。
  • WebSecurityConfigurerAdapterTests 是 Spring Security 测试套件的一部分,用于测试框架代码是否有效。所以如果你写了.antMatchers("/api/**").fullyAuthenticated(),它可以验证一个匹配“/api/”的请求会被过滤器拦截。它不能用于一般检查 Spring Security 的配置方式。

标签: spring testing spring-security


【解决方案1】:

所以您想确保如果有人将.antMatchers("/api/**") 更改为.antMatchers("/WRONG_PATH/**"),那么您有一个可以解决的测试吗?

您使用HttpSecurity 定义的规则最终会配置一个带有一个或多个SecurityFilterChainFilterChainProxy,每个SecurityFilterChain 都有一个过滤器列表。每个过滤器,如UsernamePasswordAuthenticationFilter (用于基于表单的登录),将在超类AbstractAuthenticationProcessingFilter 中定义一个RequestMatcher。问题是RequestMatcher 是一个接口,目前有 12 种不同的实现,其中包括AndRequestMatcherOrRequestMatcher,所以匹配逻辑并不总是简单的。最重要的是RequestMatcher只有一个方法boolean matches(HttpServletRequest request),并且实现通常不会暴露配置,所以你必须使用反射来访问每个RequestMatcher实现的私有配置(将来可能会改变) .

如果你沿着这条路走,将FilterChainProxy 自动连接到测试中并使用反射对配置进行逆向工程,你必须考虑你拥有的所有实现依赖项。例如WebSecurityConfigurerAdapter 有一个默认的过滤器列表,它可能会在不同版本之间发生变化,除非禁用它,当它被禁用时,您必须明确定义每个过滤器。此外,随着时间的推移,可能会添加新的过滤器和RequestMatchers,或者在一个版本的 Spring Security 中由HttpSecurity 生成的过滤器链在下一个版本中可能会略有不同(可能不太可能,但仍然可能)。

为您的 spring 安全配置编写一个通用测试在技术上是可行的,但这并不是一件容易的事,而且 Spring Security 过滤器当然不是为了支持这一点而设计的。自 2010 年以来,我与 Spring Security 进行了广泛的合作,我从未需要过这样的测试,而且我个人认为尝试实施它会浪费时间。我认为花时间编写一个测试框架会更好,这样可以轻松编写集成测试,这将隐式测试安全层以及业务逻辑。

【讨论】:

  • 路径改变场景正是我想要测试的。 “接受”,因为这回答了我的问题的根源:技术可行性。话虽如此,我有一套 MVC 和安全测试,但它们仍然感觉间接。我仍然希望能够直接验证(测试!)我的安全配置是否正确。
【解决方案2】:

我看到下面的测试用例可以帮助你实现你想要的。这是一个测试网络安全配置的集成测试,我们对所有 TDD 驱动的代码进行了类似的测试。

@RunWith(SpringRunner.class)
@SpringBootTest(classes = Application.class)
@WebAppConfiguration
public class WebConfigIT {
    private MockMvc mockMvc;

    @Autowired
    private WebApplicationContext webApplicationContext;

    @Autowired
    private FilterChainProxy springSecurityFilterChain;

    @Before
    public void setup() throws Exception {
        mockMvc = webAppContextSetup(webApplicationContext)
                .addFilter(springSecurityFilterChain)
                .build();
    }

    @Test
    public void testAuthenticationAtAPIURI() throws Exception {
        mockMvc.perform(get("/api/xyz"))
                .andExpect(status.is3xxRedirection());
    }

虽然这看起来像是对端点进行显式测试(无论如何,如果执行 TDD,则必须进行测试),但这也将 Spring 安全过滤器链带入上下文以使您能够测试安全上下文APP。

【讨论】:

  • 谢谢,但是我已经有一堆MockMvc 测试用于我的端点,所以这正是我想要的——它间接测试了一部分我的配置(一个端点)。我想直接测试我的配置。
  • 对不起,如果答案没有帮助。我仍在尝试了解您要寻找的内容是什么?如果你能把它写成Given - When - ThenGherkin格式,可能会更清楚地理解真正的要求。
【解决方案3】:

MockMVC 应该足以验证您的安全配置,因为它唯一模拟的是 Http 层。但是,如果你真的想测试你的 Spring Boot 应用程序、Tomcat 服务器等等,你需要使用@SpringBootTest,像这样

@RunWith(SpringJUnit4ClassRunner.class)
@SpringBootTest(webEnvironment= SpringBootTest.WebEnvironment.RANDOM_PORT)
public class NoGoServiceTest {

    @LocalServerPort
    private int port;

    private <T> T makeDepthRequest(NoGoRequest request, NoGoResponse response, String path, Class<T> responseClass) {
        testService.addRequestResponseMapping(request, response);

        RestTemplate template = new RestTemplate();
        HttpHeaders headers = new HttpHeaders();
        headers.setContentType(MediaType.APPLICATION_JSON);
        headers.setAccept(Lists.newArrayList(MediaType.APPLICATION_JSON));
        headers.add("Authorization", "Bearer " + tokenProvider.getToken());
        RequestEntity<NoGoRequest> requestEntity = new RequestEntity<>(request, headers, HttpMethod.POST, getURI(path));
        ResponseEntity<T> responseEntity = template.exchange(requestEntity, responseClass);
        return responseEntity.getBody();
    }

    @SneakyThrows(URISyntaxException.class)
    private URI getURI(String path) {
        return new URI("http://localhost:" +port + "/nogo" + path);
    }

    // Test that makes request using `makeDepthRequest`
}

此代码是来自开源项目 (https://github.com/maritime-web/NoGoService) 的测试的一部分。基本思想是在随机端口上启动测试,然后 Spring 会将其注入到测试的字段中。这允许您构建 URL 并使用 Springs RestTemplate 向服务器发出 http 请求,使用与控制器相同的 DTO 类。如果身份验证机制是 Basic 或 Token,您只需添加正确的 Authorization 标头,如本例所示。 如果你使用Form认证,那就有点难了,因为你首先要GET /login,然后提取CSRF令牌和JSessionId cookie,以及POST他们和/login的凭据,登录后你必须提取新的 JSessionId cookie,因为出于安全原因,登录后会更改 sessionId。

希望这是您所需要的。

【讨论】:

  • 谢谢@Klaus,但不——这不是我想要的,因为它将再次测试每个端点。我想检查我的配置并对它的状态做出断言。如果我“按端点”攻击它,那么以后的维护者有可能改变新端点的状态,而我想要的是确保针对多个端点的策略。
  • 关于每个端点的测试。在部署到生产之前,我们会运行持续部署设置,其中包括针对暂存的集成测试。在这里,我们测试所有功能,从而测试所有端点,这包括安全性。我们也有 UnitTest 用于安全性,但它们并不总是那么广泛,但如果有人破坏了简单的场景,足以获得快速的反馈循环。
  • 你也可以使用TestRestTemplate代替手动生成服务器URI
【解决方案4】:

如果您想以编程方式了解存在哪些端点,您可以将 RequestHandlerProvider 列表自动连接到您的测试中,并根据它们所暴露的路径过滤它们。

@Autowired
List<RequestHandlerProvider> handlerProviders;

@Test
public void doTest() {
    for (RequestHandlerProvider handlerProvider : handlerProviders) {
        for (RequestHandler requestHandler : handlerProvider.requestHandlers()) {
            for (String pattern : requestHandler.getPatternsCondition().getPatterns()) {
                // call the endpoint without security and check that you get 401
            }
        }
    }
} 

使用RequestHandlerProvider 是 SpringFox 在为 API 构建 swagger 定义时确定可用端点及其签名的方式。

除非您花费很长时间为每个端点构建正确的输入,否则在包含有效安全令牌时,您不会从端点返回 200 OK,因此您可能必须接受 400 作为正确响应。

如果您已经担心某些开发人员在引入新端点时会犯与安全相关的错误,我同样会担心端点的逻辑,这就是为什么我认为您应该为每个端点进行集成测试,并且这也会测试您的安全性。

【讨论】:

  • RequestHandlerProvider 是一个 SpringFox 类,但我没有使用 SpringFox。是否有 Spring MVC 类似物?我得到“没有 'java.util.List' 类型的合格 bean 可用”
  • n/m... 我发现我可以@Autowired RequestMappingHandlerMapping
  • 这更接近一点,但这仍然在“MVC”级别运行。我想检查 Spring Security 机制,而不是我的 REST 端点。
【解决方案5】:

跳出思维定势,换一种方式回答问题,简单定义一个静态String[]会不会更容易,例如

public class SecurityConfiguration extends WebSecurityConfigurerAdapter {

   public static final String[] FULLY_AUTH_PUBLIC_URLS = {"/api/**", "/swagger-resources/**", "/health", "/info" };

    protected void configure(HttpSecurity http) throws Exception {
        http
            //...
            .antMatchers(FULLY_AUTH_PUBLIC_URLS).fullyAuthenticated()
    }
}

...

如果测试的目的是确保不对公共 url 进行任何更改,只需测试已知列表?

这里的假设是 Spring Security 可以工作并且已经过测试,所以我们唯一要测试的是公共 URL 列表没有改变。如果他们更改了测试,是否应该无法向开发人员强调有龙在更改这些值?我知道这不包括说明,但假设提供的静态公共 URL 已知是准确的,那么如果需要,这种方法将提供一个可单元测试的后退。

【讨论】:

  • 这是一个很好的解决方案,我普遍接受。然而,对于大多数组织来说,一个重要的矩阵是代码覆盖率,这可能是开发人员在测试 POJO、配置和 JPARepositories 等内容时提出的创造性解决方案背后的原因
猜你喜欢
  • 2020-03-04
  • 2017-10-18
  • 2016-08-08
  • 2021-06-29
  • 2016-03-14
  • 2017-09-21
  • 2021-07-04
  • 2019-01-20
  • 2023-01-17
相关资源
最近更新 更多