Jak prawidłowo używać funkcji zapobiegania fałszowaniu żądań między lokacjami w kliencie HTTP Clojure wywołującym trasę Ring REST?

Dec 30 2020

Wciąż uczę się Clojure (i wszystkich towarzyszących bibliotek ...), więc jeśli zrobię coś głupiego w mojej niewiedzy, nie krępuj się :-)

Mam problem z wywołaniem punktów końcowych REST za pomocą POSTmetody z kodu klienta. Moje trasy są opakowane przy użyciu (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)(uważam, że jest to całkiem dobry pomysł, jeśli taki kod ma kiedykolwiek działać w środowisku produkcyjnym). Ta otoka stosuje różne inne opakowania, w tym ring.middleware.anti-forgery/wrap-anti-forgeryimplementujące (między innymi) schemat zapobiegania fałszowaniu żądań między lokacjami (CSRF lub XSRF) - domyślną (której używam) jest strategia tokenu synchronizatora (lub sesji) .

Wywołanie tego samego punktu końcowego REST via GETdziała dobrze (bo ochrona CSRF nie jest stosowana GET, HEADa OPTIONSrozmowy - patrz ring.middleware.anti-forgery/get-request?), ale przy użyciu POST(lub jeden z innych metod) Wyniki w 403 - Nieprawidłowy anty-fałszowanie żeton odpowiedź.

(Jak widać na poniższym przykładowym kodzie) Wiem, jak dodać nagłówek „X-CSRF-Token” lub „X-XSRF-Token” do żądania HTTP. (Ponieważ jest to wywołanie REST, nie dodam ukrytego pola „__anti-fałszerstwo”, jak sugeruje to pytanie i odpowiedź , chociaż jeden z nagłówków lub pole formularza wystarczyłby na opakowanie - zobacz ring.middleware.anti-forgery/default-request-token.) Raczej , jeśli dobrze rozumiem kod, mój problem wynika z faktu, że domyślna strategia porównuje powyższy token z wartością tokena sesji, która jest pobierana przez ring.middleware.anti-forgery.session/session-token:

(defn- session-token [request]
  (get-in request [:session :ring.middleware.anti-forgery/anti-forgery-token]))

Nie mam pojęcia, jak poprawnie skonfigurować informacje o sesji w wywołaniu klienta HTTP. (Dowolny klient HTTP jest wystarczający, ponieważ wspomniany powyżej wynik 403 jest generowany przez opakowanie oprogramowania pośredniego. W poniższej demonstracji używam więc prostego ring.mock.request.)

Oto minimalny kod, który pokazuje, co mam do tej pory. Definiuje trasy i programy obsługi, a następnie próbuje wywołać je z testu jednostkowego.

(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

W (println)wywołaniach są wyświetlane następujące informacje (zastosowano pewne formatowanie):

Żądanie:

{  :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>" }  }

Odpowiedź:

{  :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>"  }

Samouczki, które znalazłem, koncentrują się głównie na GETmetodzie i wydają się zakładać, że punkty końcowe / trasy będą wywoływane z HTML, który jest obsługiwany z serwera (który zawiera informacje o sesji). Więc w tej chwili czuję się trochę zablokowany.

Odpowiedzi

2 SeanCorfield Dec 30 2020 at 06:38

W przypadku interfejsu API REST należy użyć api-defaultslub secure-api-defaults NIE site-defaults .

Mechanizm zapobiegający fałszerstwom jest przeznaczony do witryn internetowych , w których aplikacja generuje formi może zawierać wygenerowany token, który ma zostać odesłany w ramach przesyłania formularza POST- nie jest przeznaczony do użytku z REST API.