【问题标题】:Is it possible to configure Spring's HttpFirewall to be invoked only on certain URL-s?是否可以将 Spring 的 HttpFirewall 配置为仅在某些 URL 上调用?
【发布时间】:2021-02-25 16:05:54
【问题描述】:

在混合 Web 应用程序中,我只有 /springpath/** 下由 Spring MVC 管理的 URI-s。我想只对/springpath/**下的资源使用Spring Security进行安全管理。在HttpSecurity 配置中,我可以使用antMatcher 排除除/springpath/** 之外的其他资源,这工作正常。
我面临的问题是StrictHttpFirewall 正在为所有请求调用,包括请求与请求 URL 中的 /springpath/** 不匹配。
是否可以从 spring 的防火墙检查中排除 URL-s?

【问题讨论】:

  • 您是否正在寻找仅适用于 /springpath/** 的所有 Spring Security(防火墙、身份验证、授权等)?
  • @RobWinch 是的。由于/springpath/** 之外的所有其他资源都不是spring 管理的,所以我想避免弄乱我不想由Spring 管理的东西。当我收到org.springframework.security.web.firewall.RequestRejectedException: The request was rejected because the URL contained a potentially malicious String "//" 时,我意识到此配置存在问题。我可以通过设置setAllowUrlEncodedDoubleSlash(true) 来解决这个错误。我不知道 webapp 的其余部分可以生成什么 URL-s,所以完全避免安全性对我来说会更好..
  • ..对于/springpath/**之外的请求。

标签: spring spring-mvc spring-security


【解决方案1】:

如果你想限制 Spring Security 对路径 /springpath/* 的请求,可以将 Servlet 容器(即 Tomcat)中的 Spring Security's FilterChainProxy 映射为仅处理 /springpath/*

春季启动

在 Spring Boot 应用程序中,Spring Boot 的 SecurityFilterConfiguration automatically registers Spring Security's FilterChainProxy 带有每个 URL。您可以通过几个步骤覆盖它:

首先,您需要使用以下内容排除 SecurityFilterAutoConfiguration

@SpringBootApplication(exclude = SecurityFilterAutoConfiguration.class)

接下来,您必须提供自己的FilterRegistrationBean。这是SecurityFilterAutoConfiguration 的稍微修改的副本,它只处理/springpath/* 内的URL。

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,但它不允许覆盖注册 FilterChainProxy 的 URL,因为通常不建议这样做。这意味着这些应用程序需要创建一个自定义 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 的单元测试不会工作,因为它不知道Servlet 容器的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

【讨论】:

  • 我正在努力找出如何为非 Spring Boot 设置实现这一点。我的AbstractSecurityWebApplicationInitializer 自动注册了一个DelegatingFilterProxy。所以我想我需要禁用自动注册并注册另一个使用自定义javax.servlet.Filter. 的DelegatingFilterProxy,但仍然无法弄清楚。任何提示都会非常有帮助。谢谢!
  • 我已经为需要实用注册安全过滤器的应用程序添加了说明(即不使用 Spring Boot)。
猜你喜欢
  • 2016-09-11
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
  • 1970-01-01
相关资源
最近更新 更多