RingRESTルートを呼び出すClojureHTTPクライアントでクロスサイトリクエストフォージェリ防止を正しく使用するにはどうすればよいですか?
私はまだClojure(およびそれに付随するすべてのライブラリ...)を学んでいるので、無知で愚かなことをした場合は、遠慮なく指摘してください:-)
POST
クライアントコードからメソッドを介してRESTエンドポイントを呼び出すのに問題があります。私のルートはを使用してラップされています(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
(または他の方法の1つを)使用すると403-無効な偽造防止トークン応答が発生します。
(以下のサンプルコードに見られるように)「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クライアント呼び出しでセッション情報を正しく設定する方法がわかりません。(上記の403の結果はミドルウェアラッパーによって生成されるため、任意のHTTPクライアントで十分です。以下のデモでは、単純なものを使用し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(セッション情報を含む)から呼び出されると想定しているようです。ですから、今は少し行き詰まっているように感じます。
回答
RESTのAPIのためには使用すべきapi-defaults
か、secure-api-defaults
しません site-defaults
。
偽造防止機構は、アプリがを生成しているWebサイト向けに設計さform
れており、フォーム送信の一部として返送される生成されたトークンを含めるPOST
ことができます。これは、RESTAPIでの使用を目的としたものではありません。