如果你想限制 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