如何在调用 Ring REST 路由的 Clojure HTTP 客户端中正确使用 Cross-Site 请求伪造预防?

How do I use Cross-Site Request Forgery prevention correctly in a Clojure HTTP client calling a Ring REST route?

我仍在学习 Clojure(以及所有那些附带的库...),所以如果我因无知做了任何愚蠢的事情,请随时指出:-)

我在客户端代码中通过 POST 方法调用 REST 端点时遇到问题。我的路由使用 (ring.middleware.defaults/wrap-defaults <my-routes> site-defaults) 包装(我相信如果这样的代码在生产中使用 运行 ,这是一个相当不错的主意)。这个包装器应用了各种其他包装器,包括 ring.middleware.anti-forgery/wrap-anti-forgery,它实现了(除其他外)Cross-Site 请求伪造(CSRF 或 XSRF)预防方案 - 默认(我正在使用的)是 synchronizer token (or session) strategy.

通过 GET 调用同一个 REST 端点工作得很好(因为 CSRF 保护不适用于 GETHEADOPTIONS 调用 - 请参阅 ring.middleware.anti-forgery/get-request?),但使用 POST(或其他方法之一)会导致 403 - Invalid anti-forgery token response.

(如以下示例代码所示)我知道如何向 HTTP 请求添加“X-CSRF-Token”或“X-XSRF-Token”header。 (由于这是一个 REST 调用,我没有按照 的建议添加隐藏的“__anti-forgery-token”字段,尽管 header 之一或表单字段会包装器就足够了 - 请参阅 ring.middleware.anti-forgery/default-request-token。)相反,如果我正确理解代码,我的问题源于这样一个事实,即默认策略将上述令牌与 session 令牌值进行比较,该值由ring.middleware.anti-forgery.session/session-token:

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

我不知道如何在 HTTP 客户端调用中正确设置 session 信息。 (任何 HTTP 客户端就足够了,因为 above-mentioned 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 方法上,并且似乎假设 endpoints/routes 会从 HTML 调用,它由服务器提供(包括 session信息)。所以我现在感觉有点卡

对于 REST API,您应该使用 api-defaultssecure-api-defaults NOT site-defaults.

防伪机制专为 Web 站点 设计,其中应用生成 form 并且可以包含生成的令牌,作为表单提交 POST -- 它不适用于 REST API.