Spring Безопасность 3.2 Поддержка CSRF для многопрофильных запросов
Мы используем Spring Security с нашим приложением в течение нескольких лет. На прошлой неделе мы обновили Spring Безопасность с версии 3.1.4 до 3.2.0. Обновление прошло нормально, и мы не обнаружили никаких ошибок после обновления.
При просмотре документации Spring Security 3.2.0 мы столкнулись с недавно добавленными функциями вокруг заголовков защиты и безопасности CSRF. Мы выполнили инструкции в документации Spring Security 3.2.0, чтобы включить защиту CSRF для наших защищенных ресурсов. Он отлично работает для обычных форм, но не работает для многочастных форм в нашем приложении. При отправке формы CsrfFilter
выдает ошибку Access Denied, ссылающуюся на отсутствие токена CSRF в запросе (определяемом через журналы DEBUG). Мы попытались использовать первый вариант, предложенный в Spring Документация по безопасности для обеспечения защиты CSRF с многочастными формами. Мы не хотим использовать второй предложенный вариант, поскольку он пропускает токены CSRF через URL-адреса и создает угрозу безопасности.
Соответствующая часть нашей конфигурации на основе документации доступна в виде Gist в Github. Мы используем Spring версию 4.0.0.
Обратите внимание, что мы уже пробовали следующие варианты без успеха:
- Не объявлять
MultipartFilter
в web.xml
.
- Не устанавливать имя преобразователя bean для
MultipartFilter
в web.xml
.
- Использование распознавателя по умолчанию bean name
filterMultipartResolver
в webContext.xml
.
ОБНОВЛЕНИЕ: Я подтвердил, что документированное поведение не работает даже с примером приложения с одной страницей. Может ли кто-нибудь подтвердить, что документированное поведение работает так, как ожидалось? Есть ли пример рабочего приложения, которое можно использовать?
Ответы
Ответ 1
Мне удалось решить эту проблему с помощью команды Spring Security. Я обновил Gist, чтобы отразить рабочую конфигурацию. Я должен был следовать приведенным ниже шагам, чтобы все работало, как ожидалось.
1. Общий шаг
Добавьте MultipartFilter
в web.xml
, как описано в статье @holmis83 в ответе выше, убедитесь, что он добавлен до Spring Конфигурация безопасности:
<filter>
<display-name>springMultipartFilter</display-name>
<filter-name>springMultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>springMultipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<display-name>springSecurityFilterChain</display-name>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
<dispatcher>ERROR</dispatcher>
<dispatcher>FORWARD</dispatcher>
<dispatcher>REQUEST</dispatcher>
</filter-mapping>
2.1. Использование многопоточного резонатора Apache Commons
Убедитесь, что в корневом контексте Spring установлен многопоточный резольвер Apache Commons bean с именем filterMultipartResolver
. Я еще раз подчеркну, что убедитесь, что многопользовательский резольвер объявлен в корневом каталоге Spring (обычно называемом applicationContext.xml). Например,
web.xml
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
classpath*:springWebMultipartContext.xml
</param-value>
</context-param>
springWebMultipartContext.xml
<beans xmlns="http://www.springframework.org/schema/beans"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://www.springframework.org/schema/beans http://www.springframework.org/schema/beans/spring-beans.xsd">
<bean id="filterMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="100000000" />
</bean>
</beans>
Убедитесь, что bean называется filterMultipartResolver, поскольку любое другое имя bean не выбрано MultipartFilter
, настроенное в web.xml
. Моя первоначальная конфигурация не работала, потому что этот bean был назван multipartResolver. Я даже попытался передать имя bean MultipartFilter
с помощью web.xml
init-param
, но это тоже не сработало.
2.2. Использование поддержки Tomcat Multipart
Tomcat 7.0+ имеет встроенную поддержку multipart, но он должен быть явно включен. Либо измените глобальный файл Tomcat context.xml
следующим образом, либо включите локальный файл context.xml
в ваш файл WAR, чтобы эта поддержка работала без внесения каких-либо изменений в ваше приложение.
<Context allowCasualMultipartParsing="true">
...
</Context>
После этих изменений с помощью Apache Commons Multipart Resolver наше приложение работает до сих пор на Tomcat, Jetty и Weblogic.
Ответ 2
Эта часть:
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<servlet-name>/*</servlet-name>
</filter-mapping>
Должно быть:
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Это ошибка в документации Spring Security 3.2.0. Сообщается об ошибке и будет исправлена в следующей версии.
Ответ 3
Немного покончив с этой проблемой, я нашел гораздо более простое решение, просто используя заголовок запроса, определенный в Spring Security, вместо того, чтобы пытаться получить токен CSRF, встроенный в состав многостраничного содержимого.
Вот простой способ настроить заголовок с помощью библиотеки AJAX для загрузки файлов в jsp:
var uploader = new AjaxUpload({
url: '/file/upload',
name: 'uploadfile',
multipart: true,
customHeaders: { '${_csrf.headerName}': '${_csrf.token}' },
...
onComplete: function(filename, response) {
...
},
onError: function( filename, type, status, response ) {
...
}
});
Который, в свою очередь, отправил многостраничный запрос с заголовком:
X-CSRF-TOKEN: abcdef01-2345-6789-abcd-ef0123456789
Их рекомендации по встраиванию в теги <meta />
в заголовке также будут очень хорошими, если остановить запрос на отправке, добавить заголовок через javascript, а затем закончить отправку:
<html>
<head>
<meta name="_csrf" content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}"/>
<!-- ... -->
</head>
<body>
<!-- ... -->
<script>
var token = $("meta[name='_csrf']").attr("content");
var header = $("meta[name='_csrf_header']").attr("content");
// Do whatever with values
</script>
</body>
</html>
Дополнительная информация: Spring Безопасность - CSRF для запросов AJAX и JSON