Можно ли настроить HttpFirewall Spring для вызова только по определенным URL-адресам?

#spring #spring-mvc #spring-security

#spring #spring-mvc #spring-безопасность

Вопрос:

В гибридном веб-приложении у меня есть только URI-ы, /springpath/** управляемые Spring MVC. Я хочу использовать Spring Security для управления безопасностью только для ресурсов под /springpath/** . В HttpSecurity конфигурации я могу использовать antMatcher для исключения других ресурсов, кроме /springpath/** , и это работает нормально.
Проблема, с которой я сталкиваюсь, StrictHttpFirewall заключается в том, что она вызывается для всех запросов, включая запросы, которые не совпадают /springpath/** с URL-адресом запроса.
Возможно ли исключить URL-адреса из проверок брандмауэра spring?

Комментарии:

1. Вы хотите, чтобы вся Spring Security (брандмауэр, аутентификация, авторизация и т. Д.) Работала только /springpath/** для?

2. @RobWinch да. Поскольку все остальные внешние ресурсы /springpath/** не управляются spring, я хочу избежать путаницы с вещами, которыми я не хочу управлять Spring. Я понял, что с этой конфигурацией возникла проблема, когда я получил org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String "//" . Я смог устранить ошибку, установив setAllowUrlEncodedDoubleSlash(true) . Я не знаю, какие URL-адреса могут быть сгенерированы остальной частью веб-приложения, поэтому для меня было бы еще лучше полностью избежать безопасности..

3. .. для запросов извне /springpath/** .

Ответ №1:

Если вы хотите ограничить запросы Spring Security путем /springpath/* , вы можете сопоставить FilterChainProxy Spring Security в контейнере сервлета (т. Е. Tomcat) только для обработки /springpath/* .

Весенняя загрузка

В приложении Spring Boot Spring Boot SecurityFilterConfiguration автоматически регистрирует Spring Security FilterChainProxy с каждым URL-адресом. Вы можете переопределить это за пару шагов:

Сначала вам нужно исключить SecurityFilterAutoConfiguration следующее:

 @SpringBootApplication(exclude = SecurityFilterAutoConfiguration.class)
  

Далее вы должны предоставить свой собственный FilterRegistrationBean . Это слегка измененная копия SecurityFilterAutoConfiguration , которая обрабатывает только URL-адреса внутри /springpath/* .

 import java.util.Collections;
import java.util.EnumSet;
import java.util.stream.Collectors;

import javax.servlet.DispatcherType;

import org.springframework.boot.autoconfigure.security.SecurityProperties;
import org.springframework.boot.web.servlet.DelegatingFilterProxyRegistrationBean;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;

@Configuration
public class SecurityFilterConfiguration {

    private static final String DEFAULT_FILTER_NAME = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;

    @Bean
    public DelegatingFilterProxyRegistrationBean securityFilterChainRegistration(
            SecurityProperties securityProperties) {
        DelegatingFilterProxyRegistrationBean registration = new DelegatingFilterProxyRegistrationBean(
                DEFAULT_FILTER_NAME);
        registration.setOrder(securityProperties.getFilter().getOrder());
        registration.setDispatcherTypes(getDispatcherTypes(securityProperties));
        registration.setUrlPatterns(Collections.singleton("/springpath/*"));
        return registration;
    }

    private EnumSet<DispatcherType> getDispatcherTypes(SecurityProperties securityProperties) {
        if (securityProperties.getFilter().getDispatcherTypes() == null) {
            return null;
        }
        return securityProperties.getFilter().getDispatcherTypes().stream()
                .map((type) -> DispatcherType.valueOf(type.name()))
                .collect(Collectors.toCollection(() -> EnumSet.noneOf(DispatcherType.class)));
    }
}
  

Вы можете добавить простой тест для проверки того, что брандмауэр не вызывается, если путь не начинается с /springpath/

 @SpringBootTest
@AutoConfigureMockMvc
public class ApplicationTests {
    @Autowired
    MockMvc mockMvc;

    @Test
    void firewallWhenSpringPathThenEnabled() {
        assertThatExceptionOfType(RequestRejectedException.class)
                .isThrownBy(() -> mockMvc.perform(request("INVALID", URI.create("/springpath/bar"))));

        assertThatExceptionOfType(RequestRejectedException.class)
                .isThrownBy(() -> mockMvc.perform(request("INVALID", URI.create("/springpath/foo/bar"))));
    }

    @Test
    void firewallWhenNotSpringPathThenNotEnabled() {
        assertThatNoException()
                .isThrownBy(() -> mockMvc.perform(request("INVALID", URI.create("/foo/bar"))));
    }
}
  

Вы можете найти полный образец по адресу https://github.com/rwinch/spring-security-sample/tree/so-64824460-firewall-subset-requests

Не загружаемые приложения

Для приложения, которое не использует Spring Boot, оно не может использовать FilterRegistrationBean . Как правило, в приложении, которое не использует Spring Boot, пользователям рекомендуется использовать AbstractSecurityWebApplicationInitializer , но это не позволяет переопределять URL, с которым FilterChainProxy зарегистрирован, поскольку это обычно не рекомендуется. Это означает, что этим приложениям необходимо создать пользовательский WebApplicationInitializer параметр для регистрации Filter , чтобы обрабатывать только подмножество URL-адресов. Код может выглядеть следующим образом:

 import java.util.EnumSet;

import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import javax.servlet.FilterRegistration;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;

import org.springframework.security.web.context.AbstractSecurityWebApplicationInitializer;
import org.springframework.util.Assert;
import org.springframework.web.WebApplicationInitializer;
import org.springframework.web.filter.DelegatingFilterProxy;

public class SecurityWebApplicationInitializer implements WebApplicationInitializer {

    @Override
    public void onStartup(ServletContext servletContext) throws ServletException {
        String filterName = AbstractSecurityWebApplicationInitializer.DEFAULT_FILTER_NAME;
        DelegatingFilterProxy springSecurityFilterChain = new DelegatingFilterProxy(filterName);
        registerFilter(servletContext, filterName, springSecurityFilterChain);
    }

    private void registerFilter(ServletContext servletContext, String filterName,
                                Filter filter) {
        FilterRegistration.Dynamic registration = servletContext.addFilter(filterName, filter);
        Assert.state(registration != null, () -> "Duplicate Filter registration for '"   filterName
                  "'. Check to ensure the Filter is only configured once.");
        EnumSet<DispatcherType> dispatcherTypes = getSecurityDispatcherTypes();
        registration.addMappingForUrlPatterns(dispatcherTypes, true, "/springpath/*");
    }

    /**
     * Get the {@link DispatcherType} for the springSecurityFilterChain.
     * @return
     */
    protected EnumSet<DispatcherType> getSecurityDispatcherTypes() {
        return EnumSet.of(DispatcherType.REQUEST, DispatcherType.ERROR, DispatcherType.ASYNC);
    }
}
  

Их все ApplicationContext равно нужно будет зарегистрировать. Обычно это можно сделать с помощью AbstractAnnotationConfigDispatcherServletInitializer . Например:

 public class MvcWebApplicationInitializer extends AbstractAnnotationConfigDispatcherServletInitializer {

    @Override
    protected Class<?>[] getRootConfigClasses() {
        return null;
    }

    @Override
    protected Class<?>[] getServletConfigClasses() {
        return new Class[] { ApplicationConfiguration.class };
    }

    @Override
    protected String[] getServletMappings() {
        return new String[] { "/" };
    }

    @Override
    protected Filter[] getServletFilters() {
        return new Filter[] { new HiddenHttpMethodFilter() };
    }

}
  

Если вы не используете Spring MVC, вы можете AbstractContextLoaderListenerInitializer вместо этого расширить.

 import org.springframework.web.context.AbstractContextLoaderInitializer;
import org.springframework.web.context.WebApplicationContext;
import org.springframework.web.context.support.AnnotationConfigWebApplicationContext;

public class WebApplicationContextInitializer extends AbstractContextLoaderInitializer {
    @Override
    protected WebApplicationContext createRootApplicationContext() {
        AnnotationConfigWebApplicationContext context = new AnnotationConfigWebApplicationContext();
        context.register(ApplicationConfiguration.class);
        return context;
    }
}
  

Модульный тест с использованием MockMvc не будет работать, потому что он не знает о контейнере сервлета FilterRegistration . Однако вы можете создать интеграционный тест, который выглядит примерно так:

 public class FirewallITests {

    private RestTemplate rest;

    private int port;

    @BeforeEach
    void setup() {
        this.port = Integer.parseInt(System.getProperty("app.httpPort"));
        this.rest = new RestTemplate();
    }

    @Test
    void firewallWhenSpringPathThenEnabled() {
        assertThatExceptionOfType(HttpServerErrorException.InternalServerError.class).
            isThrownBy(() -> this.rest.getForObject("http://localhost:"   this.port  "/springpath/;/", String.class));

        assertThatExceptionOfType(HttpServerErrorException.InternalServerError.class).
                isThrownBy(() -> this.rest.getForObject("http://localhost:"   this.port  "/springpath/foo/;/", String.class));
    }

    @Test
    void firewallWhenNotSpringPathThenNotEnabled() {
        assertThatNoException().
                isThrownBy(() -> this.rest.getForObject("http://localhost:"   this.port  "/bar/;/", String.class));
    }

}
  

https://github.com/rwinch/spring-security-sample/tree/so-64824460-firewall-subset-requests-non-boot

Комментарии:

1. Я изо всех сил пытаюсь выяснить, как реализовать это для настройки загрузки без Spring. Мой AbstractSecurityWebApplicationInitializer автоматически регистрирует DelegatingFilterProxy . Поэтому, я думаю, мне нужно отключить автоматическую регистрацию и зарегистрировать другой DelegatingFilterProxy , который использует пользовательский javax.servlet.Filter ., но все еще не могу понять это. Любые подсказки были бы очень полезны. Спасибо!

2. Я добавил инструкции для приложений, которым необходимо прагматично зарегистрировать фильтр безопасности (т. Е. Не Использовать Spring Boot).