Como faço para usar a prevenção Cross-Site Request Forgery corretamente em um cliente Clojure HTTP chamando uma rota REST de anel?

Dec 30 2020

Ainda estou aprendendo Clojure (e todas as bibliotecas que acompanham ...), então se eu fizer algo estúpido em minha ignorância, fique à vontade para apontar :-)

Tenho problemas para chamar pontos de extremidade REST por meio do POSTmétodo do código do cliente. Minhas rotas são agrupadas usando (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)(acredito que esta é uma idéia bastante boa se algum dia esse código for executado na produção). Este wrapper aplica vários outros wrappers, incluindo o ring.middleware.anti-forgery/wrap-anti-forgeryque implementa (entre outros) um esquema de prevenção Cross-Site Request Forgery (CSRF ou XSRF) - o padrão (que estou usando) é uma estratégia de token de sincronizador (ou sessão) .

Chamando o mesmo terminal REST via GETfunciona muito bem (porque a proteção CSRF não é aplicada a GET, HEADe OPTIONSchamadas - veja ring.middleware.anti-forgery/get-request?), mas utilizando POST(ou um dos outros métodos) resulta em um 403 - anti-falsificação de token inválido resposta.

(Conforme visto no código de exemplo abaixo) Eu sei como adicionar um cabeçalho "X-CSRF-Token" ou "X-XSRF-Token" à solicitação HTTP. (Como esta é uma chamada REST, não adiciono um campo oculto "__anti-forgery-token", conforme sugerido por esta pergunta e resposta , embora um dos cabeçalhos ou o campo do formulário sejam suficientes para o wrapper - consulte ring.middleware.anti-forgery/default-request-token.) , se entendi o código corretamente, meu problema decorre do fato de que a estratégia padrão compara o token acima com um valor de token de sessão, que é recuperado por ring.middleware.anti-forgery.session/session-token:

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

Não tenho ideia de como configurar as informações da sessão corretamente na chamada do cliente HTTP. (Qualquer cliente HTTP é suficiente, já que o resultado 403 mencionado acima é gerado pelo wrapper de middleware. Para a demonstração abaixo, uso o simples ring.mock.request.)

Aqui está um código mínimo que mostra o que tenho até agora. Ele define as rotas e manipuladores e, em seguida, tenta chamá-los a partir de um teste de unidade.

(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

As (println)chamadas mostram o seguinte (alguma formatação aplicada):

Solicitação:

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

Resposta:

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

Os tutoriais que encontrei concentram-se principalmente no GETmétodo e parecem assumir que os endpoints / rotas seriam chamados de HTML, que é servido a partir do servidor (que inclui informações de sessão). Então, eu me sinto um pouco preso no momento.

Respostas

2 SeanCorfield Dec 30 2020 at 06:38

Para uma API REST, você deve usar api-defaultsou secure-api-defaults NÃO site-defaults .

O mecanismo anti-falsificação é projetado para sites onde o aplicativo está gerando um forme pode incluir o token gerado para ser enviado de volta como parte do envio do formulário POST- não se destina ao uso com uma API REST.