Comment utiliser correctement la prévention de la falsification de requêtes intersites dans un client HTTP Clojure appelant une route Ring REST?
J'apprends toujours Clojure (et toutes les bibliothèques qui l'accompagnent ...), donc si je fais quelque chose de stupide dans mon ignorance, n'hésitez pas à le signaler :-)
J'ai du mal à appeler les points de terminaison REST via la POST
méthode à partir du code client. Mes routes sont encapsulées en utilisant (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)
(je pense que c'est une assez bonne idée si jamais un tel code doit être exécuté en production). Ce wrapper applique divers autres wrappers, y compris ring.middleware.anti-forgery/wrap-anti-forgery
, qui implémentent (entre autres) un schéma de prévention de la falsification de requêtes intersites (CSRF ou XSRF) - la valeur par défaut (que j'utilise) est une stratégie de jeton de synchronisation (ou de session) .
Appel du même point de terminaison REST via GET
fonctionne très bien (car la protection CSRF n'est pas appliquée GET
, HEAD
et les OPTIONS
appels - voir ring.middleware.anti-forgery/get-request?
), mais en utilisant POST
(ou l' une des autres méthodes) entraîne une 403 - jeton anti-contrefaçon non valide réponse.
(Comme vu dans l'exemple de code ci-dessous) Je sais comment ajouter un en-tête «X-CSRF-Token» ou «X-XSRF-Token» à la requête HTTP. (Comme il s'agit d'un appel REST, je n'ajoute pas de champ caché "__anti-forgery-token" comme suggéré par cette question et réponse , bien que l'un des en-têtes ou le champ du formulaire suffise pour le wrapper - voir ring.middleware.anti-forgery/default-request-token
.) , si je comprends bien le code, mon problème vient du fait que la stratégie par défaut compare le jeton ci-dessus à une valeur de jeton de session, qui est récupérée par ring.middleware.anti-forgery.session/session-token
:
(defn- session-token [request]
(get-in request [:session :ring.middleware.anti-forgery/anti-forgery-token]))
Je ne sais pas comment configurer correctement les informations de session dans l'appel client HTTP. (Tout client HTTP est suffisant, car le résultat 403 mentionné ci-dessus est généré par le wrapper middleware. Pour la démo ci-dessous, j'utilise donc le simple ring.mock.request
.)
Voici un code minimal qui montre ce que j'ai jusqu'à présent. Il définit les routes et les gestionnaires, puis essaie de les appeler à partir d'un test unitaire.
(ns question.rest
(:require [compojure.core :refer :all]
[ring.middleware.defaults :refer [wrap-defaults site-defaults secure-site-defaults]]
[ring.middleware.anti-forgery :refer [*anti-forgery-token*]]
[clojure.test :refer :all]
[ring.mock.request :as mock]))
(defroutes
exmpl-routes
(ANY "/" [] "Site up OK.")
(GET "/aft" [] (force *anti-forgery-token*)))
(def exmpl (wrap-defaults exmpl-routes site-defaults))
(deftest test-mock-fail
(testing "POST to root route"
(let [
; In a normal web app, the view/page would be GET'ed from the server, which would
; include the Anti-Forgery Token in it, and have the POST as an action on it. Hence
; the way atf is done here...
aft (:body (exmpl (mock/request :get "https://localhost:8443/aft")))
request (-> (mock/request :post "https://localhost:8443/")
(mock/header "X-CSRF-Token" aft))
_ (println request)
response (exmpl request)
_ (println response)
]
(is (= 200 (:status response))) ;;403
(is (= "Site up OK." (:body response)))))) ;;Invalid anti-forgery token
Les (println)
appels montrent ce qui suit (une certaine mise en forme a été appliquée):
Demander:
{ :protocol "HTTP/1.1",
:server-port 8443,
:server-name "localhost",
:remote-addr "localhost",
:uri "/post",
:scheme :https,
:request-method :post,
:headers { "host" "localhost:8443",
"x-csrf-token" "<long token value here>" } }
Réponse:
{ :status 403,
:headers { "Content-Type" "text/html; charset=utf-8",
"X-XSS-Protection" "1; mode=block",
"X-Frame-Options" "SAMEORIGIN",
"X-Content-Type-Options" "nosniff" },
:body "<h1>Invalid anti-forgery token</h1>" }
Les didacticiels que je pourrais trouver se concentrent principalement sur la GET
méthode et semblent supposer que les points de terminaison / routes seraient appelés à partir du HTML, qui est servi à partir du serveur (qui comprend les informations de session). Je me sens donc un peu coincé en ce moment.
Réponses
Pour une API REST, vous devez utiliser api-defaults
ou secure-api-defaults
PAS site-defaults
.
Le mécanisme anti-contrefaçon est conçu pour les sites Web où l'application génère un form
et peut inclure le jeton généré à renvoyer dans le cadre de la soumission du formulaire POST
- il n'est pas destiné à être utilisé avec une API REST.