Bagaimana cara menggunakan pencegahan Pemalsuan Permintaan Lintas Situs dengan benar di klien HTTP Clojure yang memanggil rute Ring REST?
Saya masih mempelajari Clojure (dan semua perpustakaan yang menyertainya ...), jadi jika saya melakukan sesuatu yang bodoh karena ketidaktahuan saya, silakan tunjukkan :-)
Saya mengalami masalah saat memanggil titik akhir REST melalui POST
metode dari kode klien. Rute saya dibungkus menggunakan (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)
(saya percaya ini adalah ide yang cukup bagus jika kode seperti itu dijalankan dalam produksi). Pembungkus ini menerapkan berbagai pembungkus lain, termasuk ring.middleware.anti-forgery/wrap-anti-forgery
, yang menerapkan (antara lain) skema pencegahan Pemalsuan Permintaan Lintas Situs (CSRF atau XSRF) - default (yang saya gunakan) adalah strategi token (atau sesi) penyinkron .
Memanggil titik akhir REST yang sama melalui GET
berfungsi dengan baik (karena perlindungan CSRF tidak diterapkan ke GET
, HEAD
dan OPTIONS
memanggil - lihat ring.middleware.anti-forgery/get-request?
), tetapi menggunakan POST
(atau salah satu metode lain) menghasilkan 403 - Respons token anti-pemalsuan tidak valid .
(Seperti yang terlihat pada contoh kode di bawah) Saya tahu cara menambahkan header "X-CSRF-Token" atau "X-XSRF-Token" ke permintaan HTTP. (Karena ini adalah panggilan REST, saya tidak menambahkan bidang "__anti-pemalsuan-token" tersembunyi seperti yang disarankan oleh pertanyaan dan jawaban ini , meskipun salah satu tajuk atau bidang formulir sudah cukup untuk pembungkus - lihat ring.middleware.anti-forgery/default-request-token
.) , jika saya memahami kodenya dengan benar, masalah saya berasal dari fakta bahwa strategi default membandingkan token di atas dengan nilai token sesi, yang diambil oleh ring.middleware.anti-forgery.session/session-token
:
(defn- session-token [request]
(get-in request [:session :ring.middleware.anti-forgery/anti-forgery-token]))
Saya tidak tahu cara menyiapkan informasi sesi dengan benar di panggilan klien HTTP. (Klien HTTP apa pun sudah cukup, karena hasil 403 yang disebutkan di atas dihasilkan oleh middleware wrapper. Untuk demo di bawah ini saya menggunakan yang sederhana ring.mock.request
.)
Berikut adalah beberapa kode minimal yang menunjukkan apa yang saya miliki hingga sekarang. Ini menentukan rute dan penangan, lalu mencoba memanggilnya dari pengujian unit.
(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
The (println)
panggilan menunjukkan sebagai berikut (beberapa pemformatan diterapkan):
Permintaan:
{ :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>" } }
Tanggapan:
{ :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>" }
Tutorial yang saya temukan sebagian besar berkonsentrasi pada GET
metode dan tampaknya berasumsi bahwa titik akhir / rute akan dipanggil dari HTML, yang disajikan dari server (yang mencakup info sesi). Jadi saya merasa agak mandek saat ini.
Jawaban
Untuk REST API Anda harus menggunakan api-defaults
atau secure-api-defaults
TIDAK site-defaults
.
Mesin anti-pemalsuan dirancang untuk situs web tempat aplikasi membuat form
dan dapat menyertakan token yang dihasilkan untuk dikirim kembali sebagai bagian dari pengiriman formulir POST
- ini tidak dimaksudkan untuk digunakan dengan REST API.