web-dev-qa-db-fra.com

Impossible de créer un jeton CSRF avec Spring Security

J'utilise Spring Security 3.2.3 dans mon application Spring MVC et j'obtiens un comportement inattendu. 

Selon la documentation ici , il devrait être possible d’utiliser ${_csrf.token} dans les balises META de mon code HTML:

<meta name="_csrf" content="${_csrf.token}" />
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" content="${_csrf.headerName}" />

D'où j'extrais la valeur de "contenu" à l'aide de JQuery et la place dans l'en-tête de requête à l'aide d'AJAX.

Pour une raison quelconque, Spring Security ne "convertit" pas cela en un jeton, il est simplement envoyé dans l'en-tête sous la forme d'une chaîne littérale "$ {_ csrf.token}".

En essayant le chemin alternatif consistant à utiliser ${_csrf.token} dans une entrée masquée conformément à la documentation, j’ai ensuite essayé de vérifier l’évaluation du jeton en vérifiant la valeur de l’entrée, mais il ne s’agit toujours que du texte brut "$ {_ csrf.token}".

Comme il semble que Spring Security ne soit pas en vigueur, manque-t-il une sorte de configuration? J'utilise actuellement une configuration Java de Spring Security barebones (par opposition à xml), comme indiqué ici: 

import org.springframework.context.annotation.*;
import org.springframework.security.config.annotation.web.builders.HttpSecurity;
import org.springframework.security.config.annotation.web.configuration.*;

@Configuration
@EnableWebSecurity
public class WebSecurityConfig extends WebSecurityConfigurerAdapter {

    @Override
    protected void configure(HttpSecurity http) throws Exception {
        http
          .csrf();
      }
}

Je sais que configure est appelé depuis que j'y ai placé une déclaration de débogage. Je suppose donc que la protection CSRF est bien activée puisqu'elle devrait l'être par défaut. 

Je réalise que la syntaxe "$ {}" est JSP Expression Language et je l’utilise actuellement avec succès pour évaluer le contexte dans un objet avec Thymeleaf, par exemple:

th:object="${context}"

J'ai donc essayé d'ajouter "th:" devant le "contenu" de la balise méta, comme ceci:

<meta name="_csrf" th:content="${_csrf.token}"/>

Mais il en résulte une exception selon laquelle cela ne peut pas être évalué:

Exception évaluant l'expression SpringEL: "_csrf.token"

Je pense que la clé ici est peut-être de trouver comment obtenir l'expression pour qu'elle soit correctement évaluée à mon avis.

11
starmandeluxe

J'ai finalement résolu ce problème, mais il a fallu essentiellement réécrire Spring Security. Le voici dans toute sa splendeur.

Tout d’abord, j’ai suivi les suggestions de l’excellent blog ici de Eyal Lupu, mais j’ai dû le modifier en fonction de mon besoin AJAX. 

En ce qui concerne la situation de Thymeleaf, les informations clés sont cachées dans les archives des forums de Thymeleaf - Infamous Issue 7.

https://github.com/thymeleaf/thymeleaf-spring/issues/7#issuecomment-27643488

Le dernier commentaire du créateur de Thymeleaf lui-même dit que:

th:action ... détecte quand cet attribut est appliqué sur un tag --qui devrait être le seul endroit, de toute façon--, et dans un tel cas appelle RequestDataValueProcessor.getExtraHiddenFields (...) et ajoute le retourné des champs cachés juste avant la balise de fermeture.

C'était la phrase clé dont j'avais besoin pour que le jeton fonctionne. Malheureusement, il est difficile de comprendre pourquoi th:action lancera également getExtraHiddenFields, mais en tout cas, c'est le cas, et c'est ce qui compte.

Donc, pour tous ceux qui luttent avec Thymeleaf + Spring Security CSRF + AJAX POST, voici mes étapes (ceci le réduit un peu, mais ce sont les concepts de haut niveau pour le résoudre):

  1. Implémentez l'interface RequestDataValueProcessor de Spring et enregistrez-la dans la configuration XML de Spring Security afin de pouvoir remplacer la méthode getExtraHiddenFields, ce qui vous permet d'insérer un champ de saisie masqué dans le code HTML (avec le jeton bien sûr). Le jeton lui-même est généré avec un UUID Java.Util.

  2. Avec JQuery, lisez la valeur de ce champ caché et définissez l'attribut "X-CSRF-Token" de l'en-tête de la requête afin qu'il soit envoyé via HTTP. Il n'est pas possible de simplement laisser le jeton dans le champ de saisie masqué, car nous ne faisons pas de formulaire Soumettre. Nous utilisons plutôt AJAX POST pour appeler des méthodes côté serveur.

  3. Étendez HandlerInterceptorAdapter de Spring et enregistrez-le en tant qu'intercepteur afin que, chaque fois qu'une méthode POST soit effectuée, la méthode "preHandle" côté serveur soit appelée afin de pouvoir comparer le jeton de requête (extrait de l'en-tête HTTP de la précédente procédure). étape) au jeton de la session (devrait être identique!). Une fois cette vérification effectuée, elle peut autoriser le traitement de la demande ou renvoyer une erreur.

10
starmandeluxe

J'ai commencé avec le même article source que vous, je pense, et le même "vous devriez pouvoir" ajouter des réponses comme vous l'avez fait. Je me suis battu d'une manière différente. J'ai demandé à Thymeleaf de me donner la réponse que je voulais.

<meta name="_csrf" th:content="${_csrf.token}"/>
<!-- default header name is X-CSRF-TOKEN -->
<meta name="_csrf_header" th:content="${_csrf.headerName}"/>

Thymeleaf a mis l'attribut "content" avec le contenu Spring EL demandé. J'ai ensuite utilisé JavaScript/JQuery fourni pour extraire les informations des balises META directement dans l'en-tête CSRF.

3
Tony Reynolds

Avant d'ajouter l'espace de noms thymeleaf-extras-springsecurity et sa dépendance dans mon projet, j'avais des problèmes similaires. Je n'ai jamais obtenu les balises méta au travail, même avec thymeleaf-extras-springsecurity. Mais j'ai réussi à récupérer le jeton csrf de Spring Security à l'aide de l'entrée masquée. J'ai ci-dessous des instructions qui fonctionnent pour moi: 
Dans la balise html, ajoutez: 
xmlns:sec="http://www.thymeleaf.org/thymeleaf-extras-springsecurity4"

Dans votre fichier pom.xml (si vous utilisez Maven), vous devez ajouter la dépendance: thymeleaf-extras-springsecurity4 .
Ajoutez ensuite l'entrée cachée dans le corps de votre page pour récupérer le jeton csrf. 
<input type="hidden" id= "csrf-token" th:name="${_csrf.parameterName}" th:content="${_csrf.token}" />
et ensuite utiliser cela dans votre javascript/jquery comme suit:
function f1() { var token1 = $('input#csrf-token').attr("content"); ... $.ajax({ ... type: "POST", beforeSend: function (request) { request.setRequestHeader("X-CSRF-TOKEN", token1); }, ...
Tout cela suppose que vous avez activé la sécurité de ressort et que vous n’avez PAS désactivé la protection CSRF.

3
Jon Engelbert

La configuration de springSecurityFilterChain est incorrecte dans votre fichier web.xml. La définition correcte est:

<filter>
    <filter-name>springSecurityFilterChain</filter-name>
    <filter-class>org.springframework.web.filter.DelegatingFilterProxy</filter-class>
</filter>

Spring Security utilise un ensemble de filtres de servlet pour fournir les fonctionnalités qu’il offre (y compris la protection CSRF). Ces filtres sont définis en tant que beans Spring (c’est-à-dire qu’ils sont instanciés et gérés par le contexte d’application Spring). DelegatingFilterProxy est un type spécial de filtre de servlet, qui recherche le contexte d'application root sur le contexte servlet enregistré et délègue chaque appel au même bean nommé.

1
Pavel Horal

Votre problème est différent, il est juste tombé sur celui-ci également et il m'a fallu plusieurs heures pour comprendre la cause. La cause du problème que vous avez décrit est que vous n'avez pas activé le support csrf dans votre spring-security.xml

Ce petit extrait doit aller dans votre fichier security-config.xml:

<!-- Static resources such as CSS and JS files are ignored by Spring Security -->
<security:http pattern="/static/**" security="none" />

<security:http use-expressions="true">

    <!-- Enables Spring Security CSRF protection -->
    <security:csrf/>

    <!-- Configures the form login -->
    <security:form-login
            login-page="/login"
            login-processing-url="/login/authenticate"
            authentication-failure-url="/login?error=bad_credentials"
            username-parameter="username"
            password-parameter="password"/>
    <!-- Configures the logout function -->
    <security:logout
            logout-url="/logout"
            logout-success-url="/login"
            delete-cookies="JESSIONID"/>
    <!-- Anyone can access these urls -->
    <security:intercept-url pattern="/auth/**" access="permitAll"/>
    <security:intercept-url pattern="/login" access="permitAll"/>
    <security:intercept-url pattern="/signin/**" access="permitAll"/>
    <security:intercept-url pattern="/signup/**" access="permitAll"/>
    <security:intercept-url pattern="/user/register/**" access="permitAll"/>

    <!-- The rest of our application is protected. -->
    <security:intercept-url pattern="/**" access="hasRole('ROLE_USER')"/>

    <!-- Adds social authentication filter to the Spring Security filter chain. -->
    <security:custom-filter ref="socialAuthenticationFilter" before="PRE_AUTH_FILTER" />
</security:http>
....
...
..
.

Gagnez du temps en configurant cela correctement ...

Cheerio, Flo!

0
kiteflo

Si vous n'avez pas besoin d'utiliser Thymeleaf, je suggérerais ce qui suit:

  1. Ajoutez ceci en haut de votre page:

    <%@ taglib prefix="c" uri="http://Java.Sun.com/jsp/jstl/core"%>
    
  2. Ajoutez ceci à votre formulaire de connexion:

    <input type="hidden" name="${_csrf.parameterName}" value="${_csrf.token}" />
    
  3. Ajoutez ces dépendances à votre pom.xml:

    <dependency>
        <groupId>org.Apache.Tomcat.embed</groupId>
        <artifactId>Tomcat-embed-jasper</artifactId>
        <scope>provided</scope>
    </dependency>
    <dependency>
        <groupId>javax.servlet</groupId>
        <artifactId>jstl</artifactId>
    </dependency>   
    

Après avoir beaucoup lutté, cela a fonctionné pour moi.

0
Felipe Martins Melo