问题描述
我仍在学习 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 保护不适用于 GET
、HEAD
和 OPTIONS
调用 - 请参阅 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-defaults
或 secure-api-defaults
NOT site-defaults
。
防伪机制专为应用生成 form
的网站网站而设计,并且可以包含生成的令牌作为表单提交的一部分发回 {{1 }} -- 它不适合与 REST API 一起使用。