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

问题描述

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

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

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

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

ring.middleware.anti-forgery.session/session-token

我不知道如何在 HTTP 客户端调用中正确设置会话信息。 (任何 HTTP 客户端都足够了,因为上述 403 结果是由中间件包装器生成的。对于下面的演示,我因此使用简单的 (defn- session-token [request] (get-in request [:session :ring.middleware.anti-forgery/anti-forgery-token])) 。)

这里是一些最小的代码显示了我目前所拥有的。它定义了路由和处理程序,然后尝试从单元测试中调用它们。

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>" } 方法上,并且似乎假设端点/路由将从 HTML 调用,HTML 由服务器提供(包括会话信息)。所以我现在感觉有点卡。

解决方法

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

防伪机制专为应用生成 form 的网站网站而设计,并且可以包含生成的令牌作为表单提交的一部分发回 {{1 }} -- 它不适合与 REST API 一起使用。