使用防伪和环模拟测试POST路由

Rob*_*ert 6 testing clojure mocking csrf ring

我想用一个简单的POST请求编写测试ring.mock- 如下所示:

(testing "id post route"
    (let [response (app (mock/request :post "/" {:id "Foo"}))]
      (is (= 302 (:status response)))))
Run Code Online (Sandbox Code Playgroud)

但是,由于我使用wrap-csrf中间件,因此我得到403状态响应,因为我没有提供防伪令牌.

有没有办法在ring.mock不禁用wrap-csrf中间件的情况下编写POST测试?

Pio*_*dyl 5

你想测试什么?从您的问题中不清楚,也不清楚为什么根本不应该禁用防伪中间件。

  1. 如果您正在测试 Web 服务,则根本不应使用 CSRF 令牌并切换到不同的安全机制(例如授权标头、API 令牌等)

  2. 如果您想测试包含 CSRF 逻辑的端到端流程,那么您需要首先调用适当的 URL 并从响应中提取它(例如解析隐藏字段)以及会话 ID,从而获取有效的 CSRF 令牌,以便您可以在测试请求中使用它们。

  3. 如果你想测试你的handler逻辑,那么在没有包装“基础设施”中间件的情况下测试它。如果您不能将其应用于测试中的处理程序函数并且问题消失,则模拟防伪中间件毫无意义。


csm*_*ith 5

首先,我想在另一篇文章中回应有关方法和推理的一些观点:

  1. CSRF 令牌仅对 HTML 登录是必需的,但 Web 服务不需要或不需要。
  2. 使用 Ring/Compojure,可以直接测试处理程序,或者仅将它们包装在必要的中间件中。在下面的示例中,我需要appwhich 代表整个应用程序,但如果需要,您可以绕过 CSRF 以直接测试端点。这将允许您测试给定的端点,但不会测试 CSRF 保护是否正常工作。

假设上述情况,您可能需要测试登录过程是否正常工作以及是否集成了 CSRF。以下测试助手应该可以帮助您走这条路:

(ns myapp.test.handler-test
  (:require [clojure.test :refer :all]
            [ring.mock.request :refer [request]]
            [net.cgrand.enlive-html :as html]
            [myapp.handler :refer [app]]))

(defn get-session
  "Given a response, grab out just the key=value of the ring session"
  [resp]
  (let [headers (:headers resp)
        cookies (get headers "Set-Cookie")
        session-cookies (first (filter #(.startsWith % "ring-session") cookies))
        session-pair (first (clojure.string/split session-cookies #";"))]
    session-pair))

(defn get-csrf-field
  "Given an HTML response, parse the body for an anti-forgery input field"
  [resp]
  (-> (html/select (html/html-snippet (:body resp)) [:input#__anti-forgery-token])
      first
      (get-in [:attrs :value])))

(defn get-login-session!
  "Fetch a login page and return the associated session and csrf token"
  []
  (let [resp (app (request :get "/login"))]
    {:session (get-session resp)
     :csrf (get-csrf-field resp)}))

(defn login!
  "Login a user given a username and password"
  [username password]
  (let [{:keys [csrf session]} (get-login-session!)
        req (-> (request :post "/login")
                (assoc :headers {"cookie" session})
                (assoc :params {:username username
                                :password password})
                (assoc :form-params {"__anti-forgery-token" csrf}))]
    (app req)
    session))
Run Code Online (Sandbox Code Playgroud)

在上面,我假设/login页面使用__anti-forgery-token隐藏的输入并且您想对此进行测试。您还可以考虑将 CSRF 令牌放在会话数据中,这样可以更轻松地使用诸如curl可以将会话数据从一个响应保存到文件以供后续请求使用的工具进行测试。

由于我enlive要从HTML 正文中提取令牌,因此我决定使用CSS 选择器来定义从何处以直接且声明式的方式提取此数据。

要利用上述内容,您可以调用login!并使用它在连续请求中返回的会话数据,这些请求要使用同一会话进行身份验证,例如:

 (deftest test-home-page-authentication
  (testing "authenticated response"
    (let [session (login! "bob" "bob")
          request (-> (request :get "/")
                      (assoc :headers {"cookie" session}))]
      (is (= 200 (:status (app request)))))))
Run Code Online (Sandbox Code Playgroud)

这假设您在使用的任何身份验证后端都设置了 bob 和 bob 的用户名/密码。您可能需要有一个用户名/密码设置步骤来创建此用户,然后此登录过程才能起作用。