Como faço para usar a prevenção Cross-Site Request Forgery corretamente em um cliente Clojure HTTP chamando uma rota REST de anel?
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 POST
mé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-forgery
que 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 GET
funciona muito bem (porque a proteção CSRF não é aplicada a GET
, HEAD
e OPTIONS
chamadas - 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 GET
mé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
Para uma API REST, você deve usar api-defaults
ou secure-api-defaults
NÃO site-defaults
.
O mecanismo anti-falsificação é projetado para sites onde o aplicativo está gerando um form
e 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.