Nous utilisons Spring Security avec notre application depuis quelques années maintenant. La semaine dernière, nous avons mis à niveau Spring Security de la version 3.1.4 à 3.2.0. La mise à niveau s'est bien déroulée et nous n'avons trouvé aucune erreur après la mise à niveau.
En parcourant la documentation de Spring Security 3.2.0, nous sommes tombés sur les fonctionnalités nouvellement ajoutées autour de la protection CSRF et des en-têtes de sécurité. Nous avons suivi les instructions de la documentation Spring Security 3.2.0 pour activer la protection CSRF pour nos ressources protégées. Cela fonctionne bien pour les formulaires normaux mais ne fonctionne pas pour les formulaires en plusieurs parties dans notre application. Lors de la soumission du formulaire, CsrfFilter
renvoie une erreur Accès refusé, citant l'absence d'un jeton CSRF dans la demande (déterminée par les journaux DEBUG). Nous avons essayé d'utiliser la première option suggérée dans la documentation Spring Security pour faire fonctionner la protection CSRF avec des formulaires en plusieurs parties. Nous ne souhaitons pas utiliser la deuxième option suggérée car elle fuit des jetons CSRF via les URL et pose un risque de sécurité.
La partie pertinente de notre configuration basée sur la documentation est disponible sous la forme Gist sur Github. Nous utilisons Spring version 4.0.0.
Notez que nous avons déjà essayé les variantes suivantes sans succès:
MultipartFilter
dans web.xml
.MultipartFilter
dans web.xml
.filterMultipartResolver
dans webContext.xml
.MISE À JOUR: J'ai confirmé que le comportement documenté ne fonctionne pas même avec un exemple d'application d'une seule page. Quelqu'un peut-il confirmer que le comportement documenté fonctionne comme prévu? Existe-t-il un exemple d'application de travail qui peut être utilisé?
J'ai pu résoudre ce problème avec l'aide de l'équipe Spring Security. J'ai mis à jour le Gist pour refléter une configuration de travail. J'ai dû suivre les étapes ci-dessous pour que tout fonctionne comme prévu.
1. Étape commune
Ajouter un MultipartFilter
à web.xml
comme décrit dans la réponse de @ holmis8 , en veillant à ce qu'il soit ajouté avant la configuration de Spring Security:
<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. Utilisation du résolveur Apache Commons Multipart
Assurez-vous qu'il existe un bean Apache Commons Multipart Resolver nommé filterMultipartResolver
dans le contexte de l'application Spring racine . Je vais insister à nouveau sur ce point, assurez-vous que le résolveur multi-parties est déclaré dans le contexte de printemps racine (généralement appelé applicationContext.xml). Par exemple,
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>
Assurez-vous que le bean est appelé filterMultipartResolver car tout autre nom de bean n'est pas sélectionné par MultipartFilter
configuré dans web.xml
. Ma configuration initiale ne fonctionnait pas car ce bean s'appelait multipartResolver . J'ai même essayé de passer le nom du bean à MultipartFilter
en utilisant web.xml
init-param
mais cela n'a pas fonctionné non plus.
2.2. Utilisation du support Tomcat Multipart
Tomcat 7.0+ a un support multi-parties intégré, mais il doit être explicitement activé. Soit changer le Tomcat global context.xml
comme suit ou inclure un context.xml
dans votre fichier WAR pour que cette prise en charge fonctionne sans apporter d'autres modifications à votre application.
<Context allowCasualMultipartParsing="true">
...
</Context>
Après ces changements en utilisant Apache Commons Multipart Resolver, notre application fonctionne jusqu'à présent sur Tomcat, Jetty et Weblogic.
Après avoir un peu lutté avec ce problème, j'ai trouvé une solution beaucoup plus simple en utilisant simplement l'en-tête de demande défini dans Spring Security au lieu d'essayer d'intégrer le jeton CSRF dans le cadre du contenu en plusieurs parties.
Voici une façon simple de configurer l'en-tête en utilisant une bibliothèque AJAX) pour le téléchargement de fichiers dans mon 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 ) {
...
}
});
Qui à son tour a envoyé la demande en plusieurs parties avec en-tête:
X-CSRF-TOKEN: abcdef01-2345-6789-abcd-ef0123456789
Leurs recommandations pour l'intégration dans <meta />
les balises dans l'en-tête fonctionneraient également très bien en arrêtant la demande lors de la soumission, en ajoutant l'en-tête via javascript, puis en terminant la soumission:
<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>
Plus d'informations: Spring Security - CSRF pour AJAX et requêtes JSON
Cette partie:
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<servlet-name>/*</servlet-name>
</filter-mapping>
Devrait être:
<filter-mapping>
<filter-name>multipartFilter</filter-name>
<url-pattern>/*</url-pattern>
</filter-mapping>
Il s'agit d'une erreur dans la documentation de Spring Security 3.2.0. Le bogue a été signalé et sera corrigé dans la prochaine version.
Trouver la plupart des réponses est répondu il y a des années serveur.
Si tu as besoin
Passer des jetons CSRF avec RestTemplate
Ce blog est assez instructif https://cloudnative.tips/passing-csrf-tokens-with-resttemplate-736b336a6cf6
Dans Spring Security 5.0.7.RELEASE
https://docs.spring.io/spring-security/site/docs/current/reference/html/csrf.html#csrf-multipart
Il existe deux options pour utiliser la protection CSRF avec des données multipart/form-data. Chaque option a ses compromis.
-Placement de MultipartFilter avant Spring Security
- Inclure le jeton CSRF en action
En bref, la première option est plus sûre, la seconde est plus facile.
Spécifier le MultipartFilter avant le filtre Spring Security signifie qu'il n'y a pas d'autorisation pour appeler le MultipartFilter, ce qui signifie que n'importe qui peut placer des fichiers temporaires sur votre serveur. Cependant, seuls les utilisateurs autorisés pourront soumettre un fichier traité par votre application. En général, c'est l'approche recommandée car le téléchargement de fichiers temporaires devrait avoir un impact négligeable sur la plupart des serveurs.
Pour garantir que MultipartFilter est spécifié avant le filtre Spring Security avec Java, les utilisateurs peuvent remplacer beforeSpringSecurityFilterChain comme indiqué ci-dessous:
public class SecurityApplicationInitializer extends AbstractSecurityWebApplicationInitializer { @Override protected void beforeSpringSecurityFilterChain(ServletContext servletContext) { insertFilters(servletContext, new MultipartFilter()); } }
Pour s'assurer que MultipartFilter est spécifié avant le filtre Spring Security avec configuration XML, les utilisateurs peuvent s'assurer que l'élément du MultipartFilter est placé avant le springSecurityFilterChain dans le fichier web.xml comme indiqué ci-dessous:
<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> <url-pattern>/*</url-pattern> </filter-mapping> <filter-mapping> <filter-name>springSecurityFilterChain</filter-name> <url-pattern>/*</url-pattern> </filter-mapping>
Une autre option
Si autoriser des utilisateurs non autorisés à télécharger des fichiers temporaires n'est pas acceptable, une alternative consiste à placer le MultipartFilter après le filtre Spring Security et à inclure le CSRF en tant que paramètre de requête dans l'attribut d'action du formulaire. Un exemple avec un jsp est montré ci-dessous
<form action="./upload?${_csrf.parameterName}=${_csrf.token}" method="post" enctype="multipart/form-data">
L'inconvénient de cette approche est que les paramètres de requête peuvent être divulgués. Plus généralement, il est recommandé de placer les données sensibles dans le corps ou les en-têtes pour garantir qu'elles ne fuient pas.