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