Как правильно использовать предотвращение подделки межсайтовых запросов в HTTP-клиенте Clojure, вызывающем кольцевой REST-маршрут?

Dec 30 2020

Я все еще изучаю Clojure (и все сопутствующие библиотеки ...), поэтому, если я сделаю что-нибудь глупое по своему незнанию, не стесняйтесь указывать на это :-)

У меня проблемы с вызовом конечных точек REST с помощью POSTметода из клиентского кода. Мои маршруты обернуты с использованием (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)(я считаю, что это неплохая идея, если такой код когда-либо будет запускаться в производственной среде). Эта оболочка применяет различные другие оболочки, в том числе ring.middleware.anti-forgery/wrap-anti-forgeryреализующие (среди прочего) схему предотвращения подделки межсайтовых запросов (CSRF или XSRF) - по умолчанию (которую я использую) является стратегия токена (или сеанса) синхронизатора .

Вызов той же конечной точки REST через GETработает нормально (потому что защита CSRF не применяется GET, HEADи OPTIONSвызывает - см. ring.middleware.anti-forgery/get-request?), Но использование POST(или одного из других методов) приводит к ответу токена защиты от подделки 403 - Invalid .

(Как видно из примера кода ниже) Я знаю, как добавить заголовок «X-CSRF-Token» или «X-XSRF-Token» в HTTP-запрос. (Так как это вызов REST, я не добавляю скрытое поле «__anti-forgery-token», как предлагается в этом вопросе и ответе , хотя для оболочки будет достаточно одного из заголовков или поля формы - см ring.middleware.anti-forgery/default-request-token.) Скорее. , если я правильно понимаю код, моя проблема связана с тем, что стратегия по умолчанию сравнивает указанный выше токен со значением токена сеанса, которое извлекается с помощью ring.middleware.anti-forgery.session/session-token:

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

Я понятия не имею, как правильно настроить информацию о сеансе в вызове HTTP-клиента. (Достаточно любого HTTP-клиента, поскольку вышеупомянутый результат 403 генерируется оболочкой промежуточного программного обеспечения. Таким образом, для демонстрации ниже я использую простой ring.mock.request.)

Вот небольшой код, который показывает, что у меня есть на данный момент. Он определяет маршруты и обработчики, а затем пытается вызвать их из модульного теста.

(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

На (println)звонки показывают следующее (некоторые форматирование , примененное):

Запрос:

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

Ответ:

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

Учебники, которые я мог найти, в основном сосредоточены на GETметоде и, кажется, предполагают, что конечные точки / маршруты будут вызываться из HTML, который обслуживается с сервера (который включает информацию о сеансе). Так что сейчас я чувствую себя немного застрявшим.

Ответы

2 SeanCorfield Dec 30 2020 at 06:38

Для REST API вы должны использовать api-defaultsили secure-api-defaults НЕ site-defaults .

Механизм защиты от подделки разработан для веб- сайтов, на которых приложение генерирует formи может включать сгенерированный токен для отправки обратно как часть отправки формы POST- он не предназначен для использования с REST API.