รู้ความแตกต่างระหว่างคำขอ HTTP แบบธรรมดาและ XHR
แหล่งกำเนิดเว็บแอป - https://github.com
เมื่อคุณเปิดเว็บไซต์โดยพิมพ์ Origin Web App เบราว์เซอร์ของคุณจะส่งคำขอ HTTP แบบธรรมดา CORS ใช้ไม่ได้ที่นี่ เนื่องจากเราสมัครใจที่จะให้บริการเนื้อหาจาก Origin Web App เบราว์เซอร์ของคุณจะ/ควรจะให้บริการได้โดยไม่มีปัญหาใดๆ แต่เมื่อพูดถึงคำขอ XHR JavaScript ซึ่งโหลดโดย Origin Web App ในเบราว์เซอร์ของคุณสามารถสร้างคำขอไปยังเซิร์ฟเวอร์ใดก็ได้ (https://netflix.com) ที่ร้องขอทรัพยากร ตอนนี้ Origin Web App เป็นของ Github แต่ https://netflix.com เป็นของ Netflix เซิร์ฟเวอร์นั้นสามารถให้บริการอะไรก็ได้ Github ไม่สามารถควบคุมเซิร์ฟเวอร์ที่ Netflix เป็นเจ้าของได้ สิ่งนี้มีผลกระทบด้านความปลอดภัยมากมายที่อาจขโมยเนื้อหาจากเว็บไซต์หนึ่ง (อาจเป็นเว็บไซต์ทางการเงิน) ไปยังเซิร์ฟเวอร์ระยะไกล
โชคดีที่ CORS จัดการกับปัญหานี้ได้เป็นอย่างดีด้วยกฎที่กำหนด
เป็นมาตรฐานที่กำหนดโดย W3C เพื่อเปิดใช้งานคำขอข้ามต้นทางระหว่างไคลเอนต์ (เบราว์เซอร์) และเซิร์ฟเวอร์เพื่อแบ่งปันทรัพยากรในเวลาเดียวกันเพื่อรักษาความปลอดภัย เบราว์เซอร์ใดๆ จะปฏิบัติตามมาตรฐานเหล่านี้เพื่อป้องกันการโหลดทรัพยากรจากเซิร์ฟเวอร์ของบุคคลที่สาม
ส่วนหัวระบุว่าคำขอมาจากที่ใด มันจะถูกส่งพร้อมกับคำขอ CORS เช่นเดียวกับคำขอ POST
ไวยากรณ์:
Origin: <scheme> "://" <hostname> [":" <port> ]
ตัวอย่าง:
Origin: https://netflix.com
Origin: http://netflix.com:443
Origin: http://localhost:1443
เบราว์เซอร์จะใช้ส่วนหัวนี้เมื่อออกคำขอก่อนการบินเพื่อระบุว่าจะใช้วิธีคำขอใดเมื่อมีการส่งคำขอจริง
ไวยากรณ์:
Access-Control-Request-Method: <method>
<method>
อาจเป็นได้ทั้ง GET
, POST
หรือ DELETE
ตัวอย่าง:
Access-Control-Request-Method: POST
เบราว์เซอร์จะใช้ส่วนหัวนี้อีกครั้งในคำขอก่อนการบินเพื่อระบุว่าจะใช้ส่วนหัวคำขอใดเมื่อมีการส่งคำขอจริง หากต้องการใช้หลายส่วนหัว จะต้องคั่นด้วยลูกน้ำ
ไวยากรณ์:
Access-Control-Request-Headers: <header-name>
Access-Control-Request-Headers: <header-name>, <header-name>
ตัวอย่าง:
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
สำคัญ: ส่วนหัวทั้งหมดควรอยู่ในรายการที่ปลอดภัยของ CORS หรือส่วนหัวที่กำหนดเอง เช่น X-Custom-Header
Accept
Accept-Language
Content-Language
Content-Type
ค่าที่อนุญาตคือ application/x-www-form-urlencoded
, multipart/form-data
และ text/plain
ส่วนหัวต่อไปนี้จะถูกส่งกลับในคำขอก่อนการบิน
ส่วนหัวนี้ระบุว่าหาก Origin ที่ร้องขอนั้นได้รับอนุญาต เบราว์เซอร์ของคุณจะเลือกที่จะดำเนินการตามคำขอให้สำเร็จหรือล้มเหลวโดยจับคู่ต้นทางที่ร้องขอกับสิ่งนี้
ไวยากรณ์:
Access-Control-Allow-Origin: *
สำหรับคำขอที่ไม่มีข้อมูลรับรอง ค่า *
สามารถระบุเป็นไวด์การ์ดได้ สิ่งนี้จะบอกเบราว์เซอร์ของคุณให้อนุญาตคำขอจาก Origin ใด ๆ
Access-Control-Allow-Origin: <origin>
เมื่อคุณได้รับต้นทางเพียงแห่งเดียวในส่วนหัวการตอบกลับ นั่นหมายความว่าเซิร์ฟเวอร์/เว็บแอปของคุณขึ้นอยู่กับ Origin
ที่ร้องขอ ซึ่งจะตอบกลับด้วย Origin
เดียวกันหากได้รับอนุญาต เซิร์ฟเวอร์ของคุณควรตอบสนองด้วย Vary เพื่อระบุว่าแตกต่างกันไปตามส่วนหัวของคำขอ
ตัวอย่าง:
Access-Control-Allow-Origin: https://github.com
เบราว์เซอร์ของคุณจะส่งคำขอจริงหากค่าใดค่าหนึ่งในส่วนหัวนี้ตรงกัน เมื่อส่งคืน wildcard *
หมายความว่าอนุญาตให้ใช้วิธีใดก็ได้
ไวยากรณ์:
Access-Control-Allow-Methods: <method>, <method>, ...
Access-Control-Allow-Methods: *
ตัวอย่าง:
Access-Control-Allow-Methods: GET, POST
เบราว์เซอร์ของคุณจะส่งคำขอจริงหากส่วนหัวที่ร้องขอทั้งหมดได้รับอนุญาต
ไวยากรณ์:
Access-Control-Allow-Headers: <header-name>, <header-name>
Access-Control-Allow-Headers: *
ตัวอย่าง:
Access-Control-Allow-Headers: Accept, Content-Type
Wildcard *
บอกให้เบราว์เซอร์ทราบว่าอนุญาตให้ใช้ส่วนหัวใดก็ได้
บอกว่าสามารถแคชผลลัพธ์ก่อนการบินได้นานแค่ไหน
Access-Control-Max-Age: <delta-seconds>
หมายเลขสูงสุด วินาทีก็สามารถแคชผลลัพธ์ได้
แต่ละเบราว์เซอร์มีขีดจำกัดสูงสุด
โดยทั่วไปแล้ว Vary header ใช้เพื่อวัตถุประสงค์ในการแคชเพื่อกำหนดว่าการตอบสนองที่แคชไว้สามารถใช้ได้หรือต้องแคชใหม่ตามค่าของส่วนหัว
ใน CORS สมมติว่าเซิร์ฟเวอร์อนุญาตให้มีต้นกำเนิดหลายแห่งตาม Origin
ที่ร้องขอ โดยจะส่งคืน URL เฉพาะใน Access-Control-Allow-Origin
ไวยากรณ์:
Vary: <header-name>
ตัวอย่าง:
สมมติว่า github.com อนุญาตทั้ง https://github.com และ https://netflix.com เพื่อขอทรัพยากร พิจารณาสถานการณ์ต่อไปนี้
สถานการณ์ที่ 1:
ตัวเลือก curl -X https://github.com/api/v1/gists/1
Origin: https://github.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://github.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 600
ในสถานการณ์นี้ เบราว์เซอร์จะแคชผลลัพธ์คำขอก่อนการบินนี้เป็นเวลา 10 นาที
สถานการณ์ที่ 2:
ตัวเลือก curl -X https://github.com/api/v1/gists/1
Origin: https://netflix.com
Access-Control-Request-Method: GET
Access-Control-Request-Headers: Content-Type
Access-Control-Allow-Origin: https://netflix.com
Vary: Origin
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: Content-Type
Access-Control-Max-Age: 300
ตอนนี้คุณสามารถสังเกตได้ว่า Access-Control-Allow-Origin
มี https://netflix.com นี่คือตัวอย่างการตอบสนองที่แตกต่างกันไปตาม Origin
ที่กำหนด อายุสูงสุดของการตอบสนองนี้ซึ่งถูกแคชไว้เพียง 5 นาทีไม่เหมือนกับสถานการณ์แรกเช่นกัน
คำขอบางรายการไม่ทำให้เกิดคำขอก่อนการบินสำหรับ CORS สิ่งเหล่านี้เราเรียกว่าเป็น คำของ่ายๆ ควรเป็นไปตามเงื่อนไขต่อไปนี้:
หนึ่งในวิธีที่ได้รับอนุญาต:
นอกเหนือจากส่วนหัวที่ตัวแทนผู้ใช้ดำเนินการโดยอัตโนมัติ (เช่น Connection
หรือ User-Agent
) ส่วนหัวเดียวที่ได้รับอนุญาตให้ตั้งค่าด้วยตนเองคือส่วนหัวคำขอที่แสดงรายการที่ปลอดภัยของ CORS และรายการต่อไปนี้:
DPR
Downlink
Save-Data
Viewport-Width
Width
ไม่มีการลงทะเบียนผู้ฟังเหตุการณ์บนวัตถุ XMLHttpRequestUpload
ใด ๆ ที่ใช้ในคำขอ
ไม่มีการใช้วัตถุ ReadableStream
ในคำขอ
ตัวอย่าง:
ขอ:
GET /api/v1/public-data/ HTTP/1.1
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
ส่วนหัวการตอบกลับ:
HTTP/1.1 200 OK
Access-Control-Allow-Origin: *
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml
เมื่อคำขอส่งคืน *
ในส่วนหัว Access-Control-Allow-Origin
หมายความว่าสามารถทำการร้องขอจาก Host
ใดก็ได้ ในกรณีนี้ เบราว์เซอร์ของคุณจะไม่ส่งคำขอก่อนการบิน
เบราว์เซอร์ของคุณจะส่งคำขอก่อนการบินเพื่อพิจารณาว่าคำขอจริงนั้นปลอดภัยที่จะส่งหรือไม่
ตัวอย่าง:
const xhr = new XMLHttpRequest ( ) ;
xhr . open ( 'POST' , 'https://bar.com/api/v1/post-here/' ) ;
xhr . setRequestHeader ( 'X-PINGOTHER' , 'pingpong' ) ;
xhr . setRequestHeader ( 'Content-Type' , 'application/json;charset=UTF-8' ) ;
xhr . onreadystatechange = handler ;
xhr . send ( JSON . stringify ( { "email" : "[email protected]" } ) ) ;
คำขอก่อนเที่ยวบิน:
OPTIONS /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Methods: GET, POST
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type, Accept
คำขอจริง:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
เมื่อมีการตั้งค่าส่วนหัวที่ไม่ได้มาตรฐานเช่น X-PINGOTHER
เบราว์เซอร์ของคุณจะไม่รู้ว่าจะส่งคำขอได้อย่างปลอดภัยหรือไม่ เพื่อให้แน่ใจว่าปลอดภัย เบราว์เซอร์ของคุณส่งคำขอ OPTIONS ด้วย Access-Control-Request-Headers
ที่มี X-PINGOTHER
และ Content-Type
เมื่อตรวจสอบความถูกต้องกับส่วนหัวการตอบกลับของคำขอก่อนการบิน เบราว์เซอร์ของคุณจะส่งคำขอจริง
โดยทั่วไป เมื่อคุณส่งคำขอ XHR คุกกี้จะไม่ถูกส่งไปพร้อมกับคำขอ เมื่อจำเป็นต้องส่งคุกกี้ คุณจะต้องตั้งค่าสถานะบนวัตถุ XMLHttpRequest
const xhr = new XMLHttpRequest ( ) ;
const url = 'http://bar.com/api/v1/credentialed-content/' ;
function callOtherDomain ( ) {
if ( invocation ) {
xhr . open ( 'GET' , url , true ) ;
xhr . withCredentials = true ;
xhr . onreadystatechange = handler ;
xhr . send ( ) ;
}
}
ขอ:
POST -d '{foo: bar}' /api/v1/post-here/
Host: bar.com
User-Agent: curl/7.69.1
Accept: */*
Origin: https://foo.com
X-PINGOTHER: pingpong
Content-Type: application/json;charset=UTF-8
Cookie: _session=NyV14oKXiS6HHopaf9nT
เมื่อมีการร้องขอ XMLHttpRequest
และตั้งค่าสถานะ withCredentials
เบราว์เซอร์ของคุณจะส่งผ่าน Cookie
ในส่วนหัวของคำขอ
การตอบสนอง:
HTTP/1.1 200 OK
Connection: Keep-Alive
Content-Type: application/json
Access-Control-Allow-Origin: https://foo.com
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: _session=AjBSqxj4T7bSySNTWeEm; expires=Wed, 31-05-2020 00:00:00 GMT
เมื่อเบราว์เซอร์ของคุณสังเกตเห็นการตั้งค่า Access-Control-Allow-Credentials
เป็นจริง มันจะเคารพส่วนหัว Set-Cookie
และตั้งค่าคุกกี้
สิ่งสำคัญ: ไม่ควรตั้งค่าไวด์การ์ด "*" ใน Access-Control-Allow-Origin
ตามที่กล่าวไว้ในส่วนคำขอข้อมูลรับรองและไวด์การ์ด
เมื่อทำการร้องขอข้อมูลประจำตัวโดยการตั้งค่าสถานะ withCredentials
เซิร์ฟเวอร์จะต้องตั้งค่า Access-Control-Expose-Headers
เพื่อให้เบราว์เซอร์ทราบว่าสามารถเข้าถึงส่วนหัวใดได้
ในโลกก่อนการแก้ไข โดยเบราว์เซอร์จะไม่สามารถเข้าถึงส่วนหัวการตอบกลับตามค่าเริ่มต้นในคำขอ CORS ดังนั้นจึงมีความชัดเจนเพื่อให้เบราว์เซอร์ค้นหาส่วนหัวนี้เพื่ออ่านส่วนหัวที่เปิดเผย วิธีนี้ทำให้ข้อกำหนด CORS ทำให้แน่ใจว่าเบราว์เซอร์เก่าจะไม่พัง
สิ่งนี้จะถูกส่งคืนในคำขอก่อนการบิน เมื่อเบราว์เซอร์ของคุณเห็นสิ่งนี้ ก็สามารถเข้าถึงส่วนหัว Set-Cookie
ได้ ดังที่เราได้กล่าวไว้ข้างต้น ในคำขอ XHR ปกติ เบราว์เซอร์ของคุณจะไม่ส่ง Cookie
ในส่วนหัวของคำขอ เช่นเดียวกับการอ่านส่วนหัวการตอบสนอง Set-Cookie
ไวยากรณ์:
Access-Control-Allow-Credentials: true
คุณสามารถดูตัวอย่างได้ในส่วนคำขอพร้อมข้อมูลประจำตัว
เมื่อเราพูดว่าคำขอข้อมูลรับรองหมายถึงคุกกี้ที่ส่งผ่านในคำขอ XHR หรือตั้งค่าผ่านส่วนหัวการตอบกลับ Set-Cookie
เมื่อมีการส่งคำขอ XHR ด้วยการตั้งค่าสถานะ withCredentials คุณหวังว่าจะได้รับการตอบกลับพร้อมกับคุกกี้ที่จะตั้งค่า แต่คุณไม่สามารถคาดหวังให้ Access-Control-Allow-Origin
เป็น "*" ได้ เพราะนั่นหมายความว่าเว็บไซต์ใดๆ ก็สามารถใช้คุกกี้เหล่านี้ได้ ด้วยเหตุนี้ เบราว์เซอร์ของคุณจะไม่สามารถร้องขอได้หากเห็น "*" ในส่วนหัวการตอบสนอง Access-Control-Allow-Origin
เมื่อคำขอก่อนการบินตอบกลับด้วย 301/302 เบราว์เซอร์บางตัวอาจไม่รองรับสิ่งนี้ในขณะนี้ คุณอาจได้รับข้อผิดพลาดเช่น
The request was redirected to 'https://example.com/foo', which is disallowed for cross-origin requests that require preflight
Request requires preflight, which is disallowed to follow cross-origin redirect
หมายเหตุ: สำหรับวิธีแก้ปัญหา ให้ตรวจสอบคำขอ Preflighted และเปลี่ยนเส้นทางเอกสารโดย Mozilla
เบราว์เซอร์มีการตั้งค่าให้ปฏิเสธคุกกี้ third-party
ทั้งหมด เมื่อผู้ใช้เปิดใช้งาน ตัวอย่างเช่น หากมีการร้องขอจาก https://foo.com
และเซิร์ฟเวอร์อยู่ที่ https://bar.com
เบราว์เซอร์ของคุณจะไม่ตั้งค่าคุกกี้ที่ส่งโดย https://bar.com