ฉันจะใช้การป้องกันการปลอมแปลงคำขอข้ามไซต์อย่างถูกต้องในไคลเอ็นต์ Clojure HTTP ที่เรียกเส้นทาง Ring REST ได้อย่างไร

Dec 30 2020

ฉันยังคงเรียนรู้ Clojure (และห้องสมุดที่มาพร้อมกับทั้งหมด ... ) ดังนั้นหากฉันทำอะไรโง่ ๆ โดยที่ฉันไม่รู้อย่าลังเลที่จะชี้ให้เห็น :-)

ฉันมีปัญหาในการเรียกจุดสิ้นสุด REST ผ่านPOSTวิธีการจากรหัสไคลเอนต์ เส้นทางของฉันถูกห่อโดยใช้(ring.middleware.defaults/wrap-defaults <my-routes> site-defaults)(ฉันเชื่อว่านี่เป็นความคิดที่ดีพอสมควรหากมีการเรียกใช้โค้ดดังกล่าวในการผลิต) เสื้อคลุมนี้ใช้ห่ออื่น ๆ อีกมากมายรวมทั้งring.middleware.anti-forgery/wrap-anti-forgeryซึ่งการดำเนินการ (อื่น) ขอข้ามเว็บไซต์ปลอม (CSRF หรือ XSRF) โครงการป้องกัน - เริ่มต้น (ซึ่งผมใช้) เป็นSynchronizer โทเค็น (หรือเซสชั่น) กลยุทธ์

โทรปลายทาง REST เดียวกันผ่านทางGETผลงานได้ดี (เพราะการป้องกัน CSRF ไม่ได้นำไปใช้GET, HEADและOPTIONSสาย - ดูring.middleware.anti-forgery/get-request?) แต่การใช้POST(หรือหนึ่งในวิธีการอื่น ๆ ) ผลลัพธ์ใน403 - ไม่ถูกต้องป้องกันการปลอมแปลงโทเค็นการตอบสนอง

(ดังที่เห็นในโค้ดตัวอย่างด้านล่าง) ฉันรู้วิธีเพิ่มส่วนหัว "X-CSRF-Token" หรือ "X-XSRF-Token" ในคำขอ HTTP (เนื่องจากนี่เป็นการเรียก REST ฉันจึงไม่เพิ่มฟิลด์ "__anti-ปลอมแปลง - โทเค็น" ที่ซ่อนอยู่ตามที่แนะนำโดยคำถามและคำตอบนี้แม้ว่าส่วนหัวอย่างใดอย่างหนึ่งหรือฟิลด์แบบฟอร์มจะเพียงพอสำหรับกระดาษห่อหุ้มring.middleware.anti-forgery/default-request-tokenก็ตาม- ดู) หากฉันเข้าใจรหัสอย่างถูกต้องปัญหาของฉันเกิดจากข้อเท็จจริงที่ว่ากลยุทธ์เริ่มต้นเปรียบเทียบโทเค็นข้างต้นกับค่าโทเค็นเซสชัน aa ซึ่งดึงมาจากring.middleware.anti-forgery.session/session-token:

(defn- session-token [request]
  (get-in request [:session :ring.middleware.anti-forgery/anti-forgery-token]))

ฉันไม่รู้วิธีตั้งค่าข้อมูลเซสชันอย่างถูกต้องในการเรียกไคลเอ็นต์ HTTP (ไคลเอนต์ HTTP ใด ๆ ก็เพียงพอแล้วเนื่องจากผลลัพธ์ 403 ที่กล่าวถึงข้างต้นถูกสร้างขึ้นโดยตัวห่อมิดเดิลแวร์สำหรับการสาธิตด้านล่างฉันจึงใช้วิธีง่ายๆ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>"  }

บทช่วยสอนที่ฉันพบส่วนใหญ่เน้นไปที่GETวิธีการนี้และดูเหมือนว่าจะมีการเรียกจุดสิ้นสุด / เส้นทางจาก HTML ซึ่งให้บริการจากเซิร์ฟเวอร์ (ซึ่งรวมถึงข้อมูลเซสชัน) ตอนนี้ก็เลยรู้สึกติดขัดนิดหน่อย

คำตอบ

2 SeanCorfield Dec 30 2020 at 06:38

สำหรับ REST API คุณควรใช้api-defaultsหรือไม่secure-api-defaults site-defaults

เครื่องจักรป้องกันการปลอมแปลงได้รับการออกแบบมาสำหรับเว็บไซต์ที่แอปกำลังสร้างformและสามารถรวมโทเค็นที่สร้างขึ้นเพื่อส่งกลับเป็นส่วนหนึ่งของการส่งแบบฟอร์มPOST- ไม่ได้มีไว้สำหรับใช้กับ REST API