Spring токен CSRF не работает, когда отправляемый запрос является многостраничным запросом
Я использую
- Spring Framework 4.0.0 RELEASE (GA)
- Spring Безопасность 3.2.0 RELEASE (GA)
- Struts 2.3.16
В котором я использую встроенный токен безопасности для защиты от атак CSRF.
Форма Struts выглядит следующим образом.
<s:form namespace="/admin_side"
action="Category"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
<s:hidden name="%{#attr._csrf.parameterName}"
value="%{#attr._csrf.token}"/>
</s:form>
Сгенерированный код HTML выглядит следующим образом.
<form id="dataForm"
name="dataForm"
action="/TestStruts/admin_side/Category.action"
method="POST"
enctype="multipart/form-data">
<input type="hidden"
name="_csrf"
value="3748c228-85c6-4c3f-accf-b17d1efba1c5"
id="dataForm__csrf">
</form>
Это отлично работает, если запрос не является multipart, в этом случае запрос заканчивается кодом состояния 403.
Состояние HTTP 403. Недопустимый токен CSRF "null" был найден по запросу параметр "_csrf" или заголовок "X-CSRF-TOKEN".
тип Отчет о состоянии
сообщение Недопустимый токен CSRF "null" был найден в параметре запроса '_csrf' или заголовок 'X-CSRF-TOKEN'.
описание Доступ к указанному ресурсу запрещен.
Файл spring-security.xml
выглядит следующим образом.
<?xml version="1.0" encoding="UTF-8"?>
<beans:beans xmlns="http://www.springframework.org/schema/security"
xmlns:beans="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-4.0.xsd
http://www.springframework.org/schema/security
http://www.springframework.org/schema/security/spring-security-3.2.xsd">
<http pattern="/Login.jsp*" security="none"></http>
<http auto-config='true' use-expressions="true" disable-url-rewriting="true" authentication-manager-ref="authenticationManager">
<session-management session-fixation-protection="newSession">
<concurrency-control max-sessions="1" error-if-maximum-exceeded="true" />
</session-management>
<csrf/>
<headers>
<xss-protection />
<frame-options />
<!--<cache-control />-->
<!--<hsts />-->
<content-type-options /> <!--content sniffing-->
</headers>
<intercept-url pattern="/admin_side/**" access="hasRole('ROLE_ADMIN')" requires-channel="any"/>
<form-login login-page="/admin_login/Login.action" authentication-success-handler-ref="loginSuccessHandler" authentication-failure-handler-ref="authenticationFailureHandler"/>
<logout logout-success-url="/admin_login/Login.action" invalidate-session="true" delete-cookies="JSESSIONID"/>
</http>
<beans:bean id="encoder" class="org.springframework.security.crypto.bcrypt.BCryptPasswordEncoder"/>
<beans:bean id="daoAuthenticationProvider" class="org.springframework.security.authentication.dao.DaoAuthenticationProvider">
<beans:property name="userDetailsService" ref="userDetailsService"/>
<beans:property name="passwordEncoder" ref="encoder" />
</beans:bean>
<beans:bean id="authenticationManager" class="org.springframework.security.authentication.ProviderManager">
<beans:property name="providers">
<beans:list>
<beans:ref bean="daoAuthenticationProvider" />
</beans:list>
</beans:property>
</beans:bean>
<authentication-manager>
<authentication-provider user-service-ref="userDetailsService">
</authentication-provider>
</authentication-manager>
<beans:bean id="loginSuccessHandler" class="loginsuccesshandler.LoginSuccessHandler"/>
<beans:bean id="authenticationFailureHandler" class="loginsuccesshandler.AuthenticationFailureHandler" />
<global-method-security secured-annotations="enabled" proxy-target-class="false" authentication-manager-ref="authenticationManager">
<protect-pointcut expression="execution(* admin.dao.*.*(..))" access="ROLE_ADMIN"/>
</global-method-security>
</beans:beans>
Итак, где искать этот токен, когда запрос multipart? (Это не должно быть связано с Struts вообще.)
Реализация UserDetailsService
может быть найдена в этом более раннем вопросе, если это необходимо.
Размещение MultipartFilter
до Spring Безопасность тоже не помогло.
Файл web.xml
выглядит следующим образом.
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.0"
xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee
http://java.sun.com/xml/ns/javaee/web-app_3_0.xsd">
<context-param>
<param-name>contextConfigLocation</param-name>
<param-value>
/WEB-INF/applicationContext.xml
/WEB-INF/spring-security.xml
</param-value>
</context-param>
<filter>
<filter-name>MultipartFilter</filter-name>
<filter-class>org.springframework.web.multipart.support.MultipartFilter</filter-class>
</filter>
<filter>
<filter-name>springSecurityFilterChain</filter-name>
<filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>
<filter-mapping>
<filter-name>MultipartFilter</filter-name>
<servlet-name>/*</servlet-name>
</filter-mapping>
<filter-mapping>
<filter-name>springSecurityFilterChain</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>AdminLoginNocacheFilter</filter-name>
<filter-class>filter.AdminLoginNocacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>AdminLoginNocacheFilter</filter-name>
<url-pattern>/admin_login/*</url-pattern>
</filter-mapping>
<filter>
<filter-name>NoCacheFilter</filter-name>
<filter-class>filter.NoCacheFilter</filter-class>
</filter>
<filter-mapping>
<filter-name>NoCacheFilter</filter-name>
<url-pattern>/admin_side/*</url-pattern>
</filter-mapping>
<listener>
<listener-class>org.springframework.web.context.ContextLoaderListener</listener-class>
</listener>
<listener>
<description>Description</description>
<listener-class>org.springframework.web.context.request.RequestContextListener</listener-class>
</listener>
<listener>
<listener-class>org.springframework.security.web.session.HttpSessionEventPublisher</listener-class>
</listener>
<filter>
<filter-name>struts2</filter-name>
<filter-class>org.apache.struts2.dispatcher.ng.filter.StrutsPrepareAndExecuteFilter</filter-class>
<init-param>
<param-name>struts.devMode</param-name>
<param-value>true</param-value>
</init-param>
</filter>
<filter-mapping>
<filter-name>struts2</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
<session-config>
<session-timeout>
30
</session-timeout>
</session-config>
<welcome-file-list>
<welcome-file>index.jsp</welcome-file>
</welcome-file-list>
</web-app>
Он работает только тогда, когда токен добавляется как параметр строки запроса, как показано ниже, но не рекомендуется.
<s:form namespace="/admin_side"
action="Category?%{#attr._csrf.parameterName}=%{#attr._csrf.token}"
enctype="multipart/form-data"
method="POST"
validate="true"
id="dataForm"
name="dataForm">
...
<s:form>
Ответы
Ответ 1
В этом случае, поскольку это многостраничный запрос, в котором токен CSRF недоступен для безопасности Spring, если только MultipartFilter
вместе с MultipartResolver
не настроен правильно, так что многопроцессорный запрос может быть обработан с помощью Spring.
MulipartResolver
в файле applicationContext.xml
должен быть зарегистрирован следующим образом
<bean id="filterMultipartResolver"
class="org.springframework.web.multipart.commons.CommonsMultipartResolver">
<property name="maxUploadSize" value="-1" />
</bean>
Значение атрибута -1
maxUploadSize
не ограничивает размер загруженного файла. Это значение может меняться в зависимости от требований. В случае нескольких файлов размер файла - это размер всех загруженных файлов.
Кроме того,
<servlet-name>/*</servlet-name>
of <filter-mapping>
of MultipartFilter
необходимо изменить на
<url-pattern>/*</url-pattern>
Это ошибка в документации .
Это будет работать нормально, в случае, если это только Spring MVC.
но если это интеграция Spring и Struts (2), она несет другую проблему в соответствующем классе действий Struts. Информация загруженного файла будет null
в соответствующем классе (-ях) действия Struts.
Чтобы решить эту проблему, см. этот ответ, чтобы настроить multipart request.
Ответ 2
Если вы используете @annotations и вид jsp следующим образом:
<form:form id="profileForm" action="profile?id=${param.id}" method="POST"
modelAttribute="appUser" enctype="multipart/form-data" >
...
<input type="file" name="file">
...
<input type="hidden" name="${_csrf.parameterName}"
value="${_csrf.token}" />
</form:form>
это может помочь:
AppConfig.java:
@EnableWebMvc
@Configuration
@Import({ SecurityConfig.class })
public class AppConfig {
@Bean(name = "filterMultipartResolver")
public CommonsMultipartResolver filterMultipartResolver() {
CommonsMultipartResolver filterMultipartResolver = new CommonsMultipartResolver();
filterMultipartResolver.setDefaultEncoding("utf-8");
// resolver.setMaxUploadSize(512000);
return filterMultipartResolver;
}
...
SecurityConfig.java расширяет WebSecurityConfigurerAdapter и представляет собой конфигурацию SpringSecurity
Фильтр multipart/form-data (MultipartFilter) должен быть зарегистрирован до SecurityConfig, который разрешает CSRF. Вы можете сделать это с помощью этого:
SecurityInitializer.java:
public class SecurityInitializer extends
AbstractSecurityWebApplicationInitializer {
@Override
protected void beforeSpringSecurityFilterChain(ServletContext servletContext) {
super.beforeSpringSecurityFilterChain(servletContext);
// CSRF for multipart form data filter:
FilterRegistration.Dynamic springMultipartFilter;
springMultipartFilter = servletContext.addFilter(
"springMultipartFilter", new MultipartFilter());
springMultipartFilter.addMappingForUrlPatterns(null, false, "/*");
}
}
Ответ 3
Я решил эту проблему:
- отправка многочастного файла с использованием ванильного javascript, например, в руководство Mozilla
- добавление токена _csrf в заголовке HTML в метатегах, как в Spring для отправка токена CSRF с помощью Ajax
-
вместо использования jquery, добавив его непосредственно к объекту XHR
var csrfToken = $("meta[name='_csrf']").attr("content");
var csrfHeader = $("meta[name='_csrf_header']").attr("content");
XHR.setRequestHeader(csrfHeader, csrfToken);
XHR.setRequestHeader('Content-Type','multipart/form-data; boundary=' + boundary);
XHR.send(data);
Ответ 4
Вы можете отключить csrf - httpSecurity.csrf(). disable();
@Configuration
public class SecurityConfiguration extends WebSecurityConfigurerAdapter {
@Override
protected void configure(HttpSecurity httpSecurity) throws Exception {
...
httpSecurity.csrf().disable();
...
}
}