#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).