tutorial example spring struts2 spring-security csrf csrf-protection

tutorial - spring security example



El token de Spring CSRF no funciona, cuando la solicitud que se envĂ­a es una solicitud de varias partes (4)

Si está utilizando @annotations, y la vista jsp tiene este aspecto:

<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>

esto puede ayudar:

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 amplía WebSecurityConfigurerAdapter y es la configuración de SpringSecurity

El filtro multipart / form-data (MultipartFilter) debe registrarse antes de SecurityConfig que habilita el CSRF. Puedes hacerlo con esto:

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, "/*"); } }

Yo suelo,

  • Spring Framework 4.0.0 RELEASE (GA)
  • Spring Security 3.2.0 LANZAMIENTO (GA)
  • Struts 2.3.16

En el cual, utilizo un token de seguridad incorporado para protegerme contra los ataques CSRF.

La forma Struts se parece a la siguiente.

<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>

El código HTML generado es el siguiente.

<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>

Esto funciona bien, a menos que la solicitud sea multiparte en cuyo caso, la solicitud finaliza con el código de estado 403.

Estado de HTTP 403 - Token de CSRF no válido ''nulo'' se encontró en el parámetro de solicitud ''_csrf'' o en el encabezado ''X-CSRF-TOKEN''.

tipo informe de estado

mensaje Token CSRF no válido ''null'' se encontró en el parámetro de solicitud ''_csrf'' o en el encabezado ''X-CSRF-TOKEN''.

descripción El acceso al recurso especificado ha sido prohibido.

El archivo spring-security.xml es el siguiente.

<?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>

Entonces, ¿dónde buscar este token, cuando una solicitud es multiparte? (Esto no debería estar relacionado con Struts en absoluto).

La implementación de UserDetailsService se puede encontrar en esta pregunta anterior, si es necesario.

Colocar MultipartFilter antes de Spring Security tampoco ayudó.

El archivo web.xml parece a lo siguiente.

<?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>

Solo funciona, cuando el token se anexa como un parámetro de cadena de consulta de la siguiente manera que, sin embargo, se desaconseja.

<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>


Puede deshabilitar csrf - httpSecurity.csrf (). Disable ();

@Configuration public class SecurityConfiguration extends WebSecurityConfigurerAdapter { @Override protected void configure(HttpSecurity httpSecurity) throws Exception { ... httpSecurity.csrf().disable(); ... } }


Resolví este problema por:

  • enviando el archivo de varias partes usando vainilla javascript, como en la guía de Mozilla
  • agregando el token _csrf en el encabezado HTML, en metaetiquetas, como en la guía Spring para enviar el token CSRF con Ajax
  • en lugar de usar jquery, agregarlo directamente al objeto 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);


En este caso, dado que se trata de una solicitud multiparte en la que el token CSRF no está disponible para la seguridad Spring, a menos que MultipartFilter junto con MultipartResolver esté configurado correctamente para que la solicitud multiparte pueda ser procesada por Spring.

MulipartResolver en el archivo applicationContext.xml debe registrarse de la siguiente manera

<bean id="filterMultipartResolver" class="org.springframework.web.multipart.commons.CommonsMultipartResolver"> <property name="maxUploadSize" value="-1" /> </bean>

El valor de atributo -1 de maxUploadSize no pone límite al tamaño del archivo cargado. Este valor puede variar dependiendo de los requisitos. En el caso de archivos múltiples, el tamaño del archivo es el tamaño de todos los archivos cargados.

También,

<servlet-name>/*</servlet-name>

de <filter-mapping> de MultipartFilter necesita ser cambiado a

<url-pattern>/*</url-pattern>

Esto es un error en la documentación .

Esto funcionará bien, en caso de que sea Spring MVC solo.

pero si se trata de una integración de Spring y Struts (2), incurre en otro problema en la clase de acción Struts asociada. La información del archivo cargado será null en la (s) clase (s) de acción de Struts asociada.

Para resolver este problema en particular, consulte esta respuesta para personalizar una solicitud de varias partes .