ฉันจะใช้การป้องกันการปลอมแปลงคำขอข้ามไซต์อย่างถูกต้องในไคลเอ็นต์ Clojure HTTP ที่เรียกเส้นทาง Ring REST ได้อย่างไร
ฉันยังคงเรียนรู้ 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 ซึ่งให้บริการจากเซิร์ฟเวอร์ (ซึ่งรวมถึงข้อมูลเซสชัน) ตอนนี้ก็เลยรู้สึกติดขัดนิดหน่อย
คำตอบ
สำหรับ REST API คุณควรใช้api-defaults
หรือไม่secure-api-defaults
site-defaults
เครื่องจักรป้องกันการปลอมแปลงได้รับการออกแบบมาสำหรับเว็บไซต์ที่แอปกำลังสร้างform
และสามารถรวมโทเค็นที่สร้างขึ้นเพื่อส่งกลับเป็นส่วนหนึ่งของการส่งแบบฟอร์มPOST
- ไม่ได้มีไว้สำหรับใช้กับ REST API