Clojure中无法完成POST请求

27

我最近开始尝试学习Clojure,并想要创建一个基本的CRUD功能的简单Web应用程序。我在这里找到了一个不错的教程:http://www.xuan-wu.com/2013-09-21-Basic-Web-Application-in-Clojure

GET请求正常工作,但每当我尝试进行POST请求时,都会出现以下错误:

Invalid anti-forgery token

我之前提到的教程没有涉及到任何安全相关的内容。我做了一些调查,似乎我缺少了一些Compojure的组件,用于生成用于进行POST请求的令牌。有些地方提到这应该会自动发生,无需我做出任何更改。我仍然不确定我缺少了什么。以下是我的代码:

(ns myblog.handler
    (:require [compojure.core :refer :all]
            [compojure.route :as route]
            [ring.middleware.defaults :refer [wrap-defaults site-defaults]]
            [myblog.views :as views]
            [myblog.posts :as posts]
            [ring.util.response :as resp]
            [ring.middleware.basic-authentication :refer :all]))

(defn authenticated? [name pass]
  (and (= name "user")
       (= pass "pass")))

(defroutes public-routes
    (GET "/" [] (views/main-page))
    (route/resources "/"))

(defroutes protected-routes
    (GET "/admin" [] (views/admin-page))
    (GET "/admin/add" [] (views/add-post))
    (POST "/admin/create" [& params]
        (do (posts/create params)
            (resp/redirect "/admin"))))

(defroutes app-routes
    public-routes
    (wrap-basic-authentication protected-routes authenticated?)
    (route/not-found "Not Found"))

(def app
  (wrap-defaults app-routes site-defaults))

再次强调,只有POST请求“/admin/create”失败并显示无效令牌错误。 你有什么想法我做错了什么吗?


你是如何进行POST请求的?类似于使用curl还是在浏览器中? - leeor
在浏览器中,我正在向 /admin/create 提交一个表单。 - Gregory-Turtle
3个回答

37

您的问题与wrap-defaults和site-defaults设置有关。 site-defaults默认配置添加了ant9forgery CSRF保护,任何不包含有效CSRF令牌的post请求都将被阻止。

有几种方法可以解决这个问题

  1. 使用api-defaults代替site-defaults。 api-defaults默认配置是针对提供Web API服务的站点,并关闭了site-defaults中包含的CSRF保护,后者适用于更传统的网站,其中post请求是通过以前通过get请求交付的表单生成的,并将csrf-token作为隐藏字段包含在内。这种解决方案的缺点是它可能会禁用其他想要包含的中间件。

  2. 在site-default配置中禁用csrf保护。这涉及将映射中的适当键值设置为false,如下所示:
    (wrap-defaults app-routes (assoc-in site-defaults [:security :anti-forgery] false))


4

在您的表单中添加一个隐藏字段,例如使用enlive-html:

(ns xxx.html
  (:use [net.cgrand.enlive-html])
  (:require [clojure.string :as str]
            [ring.util.anti-forgery :refer [anti-forgery-field]]))

(deftemplate render-page "base.html"
  [settings]
  [:form#my-form] (append (html-snippet (anti-forgery-field)))
  ...
)

禁用它并不被推荐,原因如下:

(def app (wrap-defaults app-routes (assoc-in site-defaults [:security :anti-forgery] false)))

(注意:保留HTML标记)

如果您正在使用Selmer,则在模板中添加隐藏字段的代码将为“{{anti-forgery-field|safe}}”,其中:anti-forgery-field是数据映射中的键。 - aboy021

3
请查看这个github 示例。特别是底部的内容:

ring-defaults默认情况下会对POST请求(和其他修改数据的请求)进行防跨站请求伪造。

如果您不想使用此功能,我认为您需要更改默认设置。
wrap-defaults routes site-defaults

如果您不想禁用它,您需要通过包含令牌的隐藏字段提交表单,或者通过 X-CSRF-TokenX-XSRF-Token 头传递它。有关详细信息,请参阅环中间件 ring-anti-forgery 的文档。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接