Compojure 应用程序默认不发送 CSRF

Compojure app not sending CSRF by default

我正在使用 reagent 和 compojure 制作一个玩具网络应用程序,但我不明白为什么我的服务器不发送 CSRF cookie。其他答案和几篇博客文章似乎暗示 compojure 的默认设置现在发送 CSRF 令牌,而手动重新发送它实际上是一个错误。当我尝试访问 POST /art 端点时,我得到了 403 Forbidden 响应。 None 的页面获取其中包含 CSRF 令牌的 cookie,因此我无法通过 POST 请求发送它。有什么建议吗?

;;server.clj

(ns my-app.server
  (:require [my-app.handler :refer [app]]
            [environ.core :refer [env]]
            [ring.adapter.jetty :refer [run-jetty]])
  (:gen-class))

 (defn -main [& args]
   (let [port (Integer/parseInt (or (env :port) "3000"))]
     (run-jetty app {:port port :join? false})))

;; handler.clj

(ns my-app.handler
  (:require [compojure.core :refer [GET POST defroutes]]
            [compojure.route :refer [not-found resources]]
            [hiccup.page :refer [include-js include-css html5]]
            [my-app.middleware :refer [wrap-middleware]]
            [environ.core :refer [env]]))


(defroutes routes
  (GET "/" [] loading-page)
  (GET "/about" [] loading-page)
  (GET "/art" [] loading-page)
  (POST "/art" request {:sent (:body request) :hello "world"})

  (resources "/")
  (not-found "Not Found"))

(def app (wrap-middleware #'routes))


;;middleware.clj

(ns stagistry.middleware
  (:require [ring.middleware.defaults :refer [site-defaults wrap-defaults]]
            [prone.middleware :refer [wrap-exceptions]]
            [ring.middleware.reload :refer [wrap-reload]]))

(defn wrap-middleware [handler]
  (-> handler
      (wrap-defaults site-defaults)
      wrap-exceptions
      wrap-reload))

我把代码本身放在 github here 上,因为我仍然看不出有什么问题。

所以我找到了一个相关问题的答案,目前对我来说已经足够好了。我将 middleware.clj site-defaults 切换为不使用 CSRF 的 api-defaults。仍然好奇如何让 CSRF 在这里工作,但这对我正在做的事情并不重要。如果后来有人提出有效的修复方法,我会将其标记为正确。

Other answers and several blog posts seem to imply that the default settings for compojure now send the CSRF token and that manually resending it is actually a bug.

(wrap-defaults site-defaults) 应用 ring-anti-forgery 中间件。这只会向每个环形浏览器 session 添加一个 CSRF 令牌,并在 POST 请求中查找令牌。如果缺少令牌,中间件将为请求 return 一个 403。将令牌添加到您的表单或 ajax/whatever post 请求由您决定,这是自由的代价。 :)

来自 ring-anti-forgery 文档:

By default, the token is expected to be in a form field named '__anti-forgery-token', or in the 'X-CSRF-Token' or 'X-XSRF-Token' headers.

例如尝试添加这条路线:

(GET "/someform" request (html5 (ring.util.anti-forgery/anti-forgery-field)))

anti-forgery-field 帮助程序将添加一个隐藏的输入字段,其中包含 CSRF 令牌作为其值,如果表单是 posted,中间件将拾取该输入字段。要直接访问令牌,您可以使用 ring.middleware.anti-forgery/*anti-forgery-token* 或在当前请求映射的 session 中查找它:

(-> request :session :ring.middleware.anti-forgery/anti-forgery-token)

虽然全局变量(以及扩展助手)是 bound 处理程序上下文,但您不能从外部或同一上下文中的另一个线程访问它。

简单卷曲header示例:

  1. 获取 CSRF 令牌:

    $ curl -v -c /tmp/cookiestore.txt http://localhost:3000/someform
    

  2. 通过 header 和 post 设置令牌:

    $ curl -v -b /tmp/cookiestore.txt --header "X-CSRF-Token: ->token from prev. req<-" -X POST -d '{:foo "bar"}' localhost:3000/art