Spring Тестирование конфигурации безопасности HttpSecurity

У меня есть приложение 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.RELEASE, spring-security-core-4.2.2-release.

Clarification1: Я хочу как можно скорее проверить SecurityConfiguration, а не транзитивно тестировать через одну из конечных точек /api/**, которые могут иметь свои собственные @PreAuthorize безопасности.

Clarification2: Я хотел бы что-то подобное этому WebSecurityConfigurerAdapterTests.

Clarification3: Я хотел бы @Autowire что-то на уровне безопасности Spring, в идеале HttpSecurity, чтобы проверить.

Ответ 1

Итак, вы хотите убедиться, что если кто-то изменит .antMatchers("/api/**") на .antMatchers("/WRONG_PATH/**"), значит, у вас есть тест, который выяснит это?

Правила, которые вы определяете с помощью HttpSecurity, закончите настройку FilterChainProxy с одним или несколькими SecurityFilterChain, каждый со списком фильтров. Каждый фильтр, например UsernamePasswordAuthenticationFilter (используется для входа в форму), будет иметь RequestMatcher, определенный в суперклассе AbstractAuthenticationProcessingFilter. Проблема в том, что RequestMatcher - это интерфейс, который в настоящее время имеет 12 различных реализаций, и включает в себя AndRequestMatcher и OrRequestMatcher, поэтому логика соответствия не всегда проста. И самое главное RequestMatcher имеет только один метод boolean matches(HttpServletRequest request), и реализация часто не раскрывает конфигурацию, поэтому вам придется использовать рефлексию для доступа к приватным конфигурациям каждой реализации RequestMatcher (которая может измениться в будущем).

Если вы перейдете по этому пути и запустите autwire FilterChainProxy в тест и используйте рефлексию для обратной настройки конфигурации, вы должны рассмотреть все зависимости от реализации, которые у вас есть. Например, WebSecurityConfigurerAdapter имеет список фильтров по умолчанию, который может меняться между версиями, и если не отключить его, а когда он отключен, вы должны явно определить каждый фильтр. Кроме того, с течением времени могут быть добавлены новые фильтры и RequestMatchers, или цепочка фильтров, сгенерированная HttpSecurity в одной версии Spring Security, может немного отличаться в следующей версии (может быть, это маловероятно, но все же возможно).

Написание общего теста для вашей конфигурации безопасности Spring является технически возможным, но это не совсем просто, и фильтры безопасности Spring, безусловно, не были предназначены для поддержки этого. Я много работал с защитой Spring с 2010 года, и у меня никогда не было необходимости в таком тесте, и лично я думаю, что было бы пустой тратой времени на ее реализацию. Я думаю, что время будет намного лучше потратить на создание тестовой среды, что упростит запись теста интеграции, который будет неявно протестировать уровень безопасности, а также бизнес-логику.

Ответ 2

Я вижу ниже тестовый пример, который поможет вам достичь того, чего вы хотите. Это тест интеграции для тестирования конфигурации Web Security, и мы проводим аналогичное тестирование для всего нашего кода, который управляется 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 в контексте, чтобы вы могли протестировать Security Контекст для APP.

Ответ 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, вам просто нужно добавить правильный заголовок авторизации, как в этом примере. Если вы используете аутентификацию по форме, это становится немного сложнее, потому что вам сначала нужно GET /login, а затем извлечь токен CSRF и файл cookie JSessionId и POST с учетными данными до /login, а после входа в систему вы необходимо извлечь новый файл cookie JSessionId, поскольку sessionId изменяется после входа в систему по соображениям безопасности.

Надеюсь, это было то, что вам нужно.

Ответ 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 определяет, какая конечная точка доступна и их подпись, когда она строит определение swagger для API.

Если вы не тратите много времени на создание правильного ввода для каждой конечной точки, вы не получите 200 OK обратно с конечной точки при включении действительного маркера безопасности, поэтому вам, вероятно, придется принять 400 в качестве правильного ответа.

Если вы уже обеспокоены тем, что какой-либо разработчик допустил ошибки, связанные с безопасностью при вводе новой конечной точки, я был бы в равной степени обеспокоен логикой конечной точки, поэтому я думаю, что у вас должен быть интеграционный тест для каждого из них. что также проверит вашу безопасность.

Ответ 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 Безопасность работает и была протестирована, поэтому единственное, что мы тестируем, это то, что список общедоступных URL-адресов не был изменен. Если они изменили тест, он не должен выделять разработчику, что есть драконы, изменяющие эти значения? Я понимаю, что это не распространяется на пояснения, но если известно, что предоставленные статические общедоступные URL-адреса являются точными, то этот подход обеспечит единый проверяемый обратный останов, если это необходимо.