Soalan dan Masa Ia Digunakan
Halaman dijalankan di https://app.example.com, manakala API berada di https://api.example.com. Frontend menghantar keutamaan pengguna:
await fetch("https://api.example.com/profile/preferences", {
method: "POST",
credentials: "include",
headers: {
"Content-Type": "application/json",
"X-CSRF-Token": csrfToken,
},
body: JSON.stringify({ compactMode: true }),
})Pelayan pada masa ini mengembalikan respons ini kepada OPTIONS:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: *
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: GET, POSTPOST terus melalui curl berjaya, tetapi konsol pelayar hanya melaporkan ralat rentas asal. Jelaskan:
- mengapa kedua-dua subdomain tersebut masih bersifat rentas asal;
- mengapa pelayar menghantar OPTIONS terlebih dahulu dan bila ia akan meneruskan dengan POST;
- apakah yang tidak kena dengan pengepala respons semasa dan cara menggunakan pembaikan keistimewaan paling rendah (least-privilege);
- tanggungjawab berasingan bagi CORS, kuki, pengesahan (authentication), kebenaran (authorization), dan CSRF;
- cara membuktikan pembaikan tersebut dalam alatan pembangun (developer tools) dan ujian automatik.
Ini berguna untuk temu duga frontend dan full-stack. Ujian ini bukan tentang sama ada seseorang boleh menghafal set pengepala. Ia adalah sama ada mereka boleh menganalisis secara undur dari fasa rangkaian pelayar dan memisahkan empat soalan: adakah permintaan dihantar, adakah kelayakan dilampirkan, bolehkah JavaScript membaca respons, dan adakah aplikasi membenarkan operasi tersebut?
Perkara yang Dinilai oleh Penemu Duga
Pertama, calon perlu mengenal pasti asal (origin) dengan tepat. Sesuatu asal terdiri daripada skema, hos, dan port. Kedua-dua URL menggunakan HTTPS dan biasanya sama tapak (same-site), tetapi hosnya berbeza, jadi permintaan tersebut adalah sama tapak dan rentas asal. CORS adalah berasaskan asal; atribut kuki SameSite adalah berasaskan tapak. Kedua-duanya tidak boleh ditukar ganti.
Kedua, calon perlu memahami pencetus preflight. application/json bukanlah nilai pengepala permintaan yang tersenarai selamat (CORS-safelisted), dan X-CSRF-Token bukanlah pengepala permintaan yang tersenarai selamat. Pelayar oleh itu menghantar OPTIONS untuk bertanya sama ada kaedah dan pengepala sebenar dibenarkan. Jika preflight gagal, POST tidak dihantar.
Ketiga, calon perlu mengesan dua kesilapan konfigurasi secara langsung. Respons berkelayakan tidak boleh menggunakan Access-Control-Allow-Origin: *, dan respons preflight tidak membenarkan Content-Type atau X-CSRF-Token. Menyenaraikan POST di bawah kaedah yang dibenarkan sahaja adalah tidak mencukupi.
Akhir sekali, calon mesti mengekalkan sempadan keselamatan. CORS mengawal sama ada pelayar berkongsi respons rentas asal dengan skrip. Ia tidak menggantikan pengesahan, kebenaran, atau perlindungan CSRF. Pelayan tidak boleh memantulkan sebarang asal sewenang-wenangnya semata-mata untuk mendiamkan konsol atau membenarkan setiap kaedah dan pengepala.
Soalan untuk Dijelaskan Terlebih Dahulu
- Pada fasa rangkaian manakah ia gagal? Tiada permintaan, OPTIONS sahaja, atau POST yang selesai tetapi responsnya
tidak boleh diakses oleh JavaScript menunjukkan kegagalan yang berbeza.
- Adakah permintaan mesti membawa kuki? Token pembawa (bearer token) atau sumber awam tanpa nama mengubah dasar
kelayakan dan asal yang dibenarkan.
- Asal frontend tepat yang manakah dibenarkan? Pengeluaran (production), pementasan (staging), dan pembangunan tempatan memerlukan asal
yang eksplisit, termasuk skema dan port. "Domain syarikat" adalah tidak cukup tepat.
- Adakah get laluan (gateway), middleware pengesahan, atau lencongan memintas OPTIONS? Preflight biasanya tidak mempunyai
kelayakan pengguna, jadi ia tidak boleh diwajibkan untuk melepasi pengesahan log masuk terlebih dahulu.
- Apakah atribut Domain, Path, Secure, dan SameSite bagi kuki tersebut? Melepasi CORS tidak menjamin
bahawa pelayar menghantar kuki.
- Adakah respons sebenar dan ralat semuanya membawa pengepala CORS? Jika respons 401, 403, atau 500 kehilangan pengepala asal
yang dibenarkan, pelayar mungkin menyembunyikan status sebenar di sebalik ralat CORS generik.
- Adakah terdapat CDN atau cache kongsi? Jika respons berbeza mengikut Origin, hantar
Vary: Originsupaya respons satu asal
tidak diguna semula untuk asal yang lain.
Rangka Jawapan 30 Saat
"Saya akan membahagikan ini kepada empat pintu kawalan: asal, kebenaran preflight, penghantaran kelayakan, dan perkongsian respons. Hosnya berbeza, jadi permintaan tersebut adalah rentas asal. Content-Type JSON dan X-CSRF-Token mencetuskan preflight OPTIONS. Pelayar menghantar POST hanya jika respons membenarkan asal yang tepat, POST, dan kedua-dua pengepala. Oleh sebab fetch menggunakan credentials: include, Allow-Origin tidak boleh menjadi kad bebas (wildcard). Selepas padanan senarai dibenarkan yang tepat, pelayan harus mengembalikan https://app.example.com, Allow-Credentials, dan Vary: Origin. OPTIONS itu sendiri tidak bergantung pada kuki log masuk; POST masih memerlukan pengesahan, kebenaran, dan pengesahan CSRF. Saya akan mengesahkan OPTIONS, POST, kuki, pengepala respons, dan keadaan aplikasi dalam panel Network, kemudian menjalankan matriks ujian untuk asal positif dan asal bermusuhan."
Analisis Mendalam Langkah demi Langkah
Langkah satu: modelkan mesin keadaan (state machine) yang sebenarnya dijalankan oleh pelayar.
Preflight adalah lebih kurang seperti berikut:
OPTIONS /profile/preferences HTTP/1.1
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: content-type,x-csrf-tokenPelayar tidak bertanya sama ada frontend dan backend adalah milik syarikat yang sama. Ia bertanya sama ada respons secara eksplisit membenarkan Origin, kaedah, dan set pengepala ini. Bukti berikut mengasingkan setiap fasa:
| Bukti rangkaian | Maksud | Pemeriksaan seterusnya |
|---|---|---|
| Tiada OPTIONS atau POST | JavaScript tidak dijalankan, atau peraturan terdahulu seperti CSP menyekat URL | Periksa panggilan dan konsol terlebih dahulu |
| Hanya OPTIONS yang gagal | POST tidak dihantar | Periksa status, lencongan, dan pengepala kebenaran |
| OPTIONS berjaya; POST tiada kuki | Kebenaran CORS lepas; penghantaran kelayakan tidak lepas | Periksa credentials dan atribut kuki |
| POST mengembalikan 401/403/500, tetapi skrip hanya melihat CORS | Respons ralat kekurangan pengepala CORS yang sah | Kekalkan status dan tambah pengepala asal yang dibenarkan |
| POST berjaya dan skrip boleh membacanya | Laluan CORS lepas | Sahkan keadaan aplikasi dan ujian negatif keselamatan |
Langkah dua: terbitkan set kebenaran minimum daripada permintaan.
Bagi frontend pengeluaran tunggal dalam soalan tersebut, kembalikan:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, X-CSRF-Token
Vary: OriginPelayan harus membandingkan Origin dengan senarai dibenarkan yang tepat dan menulisnya kembali hanya selepas terdapat padanan. Jangan salin Origin yang dibekalkan oleh klien tanpa syarat. Jika persekitaran pementasan dan tempatan turut dibenarkan, senaraikan setiap asal yang lengkap dalam persekitaran yang sepadan. Padanan akhiran boleh menerima domain penyerang yang kelihatan serupa.
Kad bebas boleh menjadi sesuai untuk sumber awam, tanpa nama, dan tidak berkelayakan. Titik akhir keutamaan pengguna ini membawa kuki dan melakukan operasi sensitif, jadi ia memerlukan asal yang khusus. Preflight OPTIONS tidak membawa kuki. Get laluan boleh mengendalikannya sebelum pengesahan sambil tetap mengesahkan Origin, kaedah, dan pengepala permintaan secara ketat.
Langkah tiga: periksa permintaan sebenar dan kelayakan secara berasingan.
Pelayar menghantar POST hanya selepas preflight berjaya. credentials: "include" membolehkan permintaan rentas asal memasuki pemprosesan kelayakan, tetapi Domain, Path, Secure, SameSite, dan dasar privasi pelayar tetap menentukan sama ada kuki dilampirkan.
Kedua-dua subdomain HTTPS ini biasanya sama tapak dan rentas asal. SameSite mungkin membenarkan kuki manakala CORS masih terpakai. "CORS lepas" tidak bermaksud "kuki telah dihantar," dan "kuki telah dihantar" tidak bermaksud "skrip boleh membaca respons." Periksa Request Cookies, status respons, dan pengepala respons CORS sebagai fakta berasingan dalam panel Network.
Langkah empat: asingkan CORS daripada keselamatan aplikasi.
CORS ialah protokol perkongsian respons pelayar, bukan kawalan akses bahagian pelayan. Curl dan klien HTTP bahagian pelayan tidak menguatkuasakan dasar sama asal (same-origin policy) pelayar. Panggilan curl yang berjaya membuktikan bahawa API boleh mengendalikan permintaan tersebut, bukannya membuktikan laluan pelayar adalah betul.
POST masih mesti:
- mengesahkan pengguna (authenticate);
- membenarkan pengguna tersebut memodifikasi sumber sasaran (authorize);
- mengesahkan token CSRF atau menggunakan pertahanan yang sama kukuh;
- mengesahkan input dan menentukan kelakuan keneutralan operasi (idempotency) atau penghantaran pendua;
- mengembalikan pengepala CORS yang betul pada respons 401, 403, dan 500 bagi asal yang dibenarkan supaya frontend melihat ralat sebenar.
"Halaman bermusuhan tidak boleh membaca respons" tidak bermaksud "permintaan itu tidak mempunyai kesan sampingan." Sesetengah permintaan rentas asal berbentuk borang tidak memerlukan preflight dan boleh sampai ke pelayan. Operasi penulisan sensitif tidak boleh bergantung pada CORS semata-mata untuk perlindungan CSRF.
Langkah lima: buktikan sempadan dengan ujian positif dan negatif.
Selepas pembaikan, sahkan sekurang-kurangnya:
| Kes | Hasil yang dijangkakan |
|---|---|
| Asal yang dibenarkan menghantar permintaan yang dinyatakan | OPTIONS 204, kemudian POST; skrip membaca respons sebenar |
| Asal yang dibenarkan meninggalkan token CSRF | Konfigurasi protokol menentukan preflight; pengesahan aplikasi menolak penulisan |
| Asal yang tidak dibenarkan menghantar permintaan yang sama | Tiada pengepala CORS membenarkan asal tersebut; preflight tidak lepas |
| Permintaan menggunakan kaedah atau pengepala yang tidak dibenarkan | Preflight gagal, POST tiada, dan keadaan tidak berubah |
| Kuki log masuk telah tamat tempoh | POST mengembalikan 401 yang boleh dibaca dan frontend memulakan aliran log masuknya |
| Pelayan mengembalikan 500 | Frontend melihat 500 dan ralat berstruktur, bukannya mesej CORS generik |
| Permintaan berulang menggunakan cache preflight | Kelakuan kekal betul; perubahan konfigurasi juga diuji dengan cache sejuk |
Hubungkaitkan ini dengan log pelayan. Preflight yang gagal tidak boleh mempunyai POST yang sepadan dan tiada kemas kini keutamaan. Log boleh merekodkan Origin, kaedah, nama pengepala, dan sebab penolakan, tetapi bukan nilai kuki atau token CSRF.
Contoh Jawapan yang Mantap
"Saya akan mengenal pasti fasa rangkaian terlebih dahulu. app.example.com dan api.example.com mempunyai hos yang berbeza, jadi ia adalah rentas asal walaupun ia adalah sama tapak. POST menggunakan Content-Type JSON dan X-CSRF-Token, jadi pelayar terlebih dahulu menghantar permintaan OPTIONS tanpa kuki. Ia mengisytiharkan Origin, POST, dan kedua-dua pengepala bukan mudah. Pelayar menghantar POST hanya jika respons preflight membenarkan ketiga-tiganya.
Konfigurasi semasa mempunyai dua kecacatan langsung. Pertama, frontend menetapkan credentials: include, jadi respons berkelayakan tidak boleh menggunakan kad bebas untuk Access-Control-Allow-Origin. Kedua, respons tidak menyertakan Access-Control-Allow-Headers: Content-Type, X-CSRF-Token. Saya akan mengesahkan Origin terhadap senarai dibenarkan pelayan yang tepat. Untuk https://app.example.com, saya akan mengembalikan nilai khusus tersebut, Allow-Credentials, POST, kedua-dua pengepala yang dibenarkan, dan Vary: Origin. OPTIONS boleh dikendalikan sebelum middleware pengesahan pengguna, tetapi asal, kaedah, atau pengepala yang tidak dibenarkan tetap ditolak.
Selepas preflight lepas, saya akan memeriksa Request Cookies pada POST. credentials: include adalah perlu tetapi tidak mencukupi; Domain, Path, Secure, SameSite, dan dasar pelayar masih terpakai. POST mesti terus mengesahkan, membenarkan, dan mengesahkan CSRF kerana CORS mengawal perkongsian respons, bukan kawalan akses.
Untuk pengesahan, saya akan memastikan dalam panel Network bahawa POST hanya muncul selepas OPTIONS berjaya, kemudian memeriksa kuki, status sebenar, dan pengepala respons. Saya akan menguji asal yang dibenarkan, asal yang tidak dibenarkan, kaedah yang tidak dibenarkan, log masuk yang tamat tempoh, dan respons 500. Log pelayan mesti membuktikan bahawa preflight yang gagal tidak mempunyai POST dan tiada perubahan keadaan. Curl ialah garis dasar API, bukan pengganti kepada bukti CORS pelayar."
Kesilapan Lazim
- Menganggap sama tapak (same-site) sebagai sama asal (same-origin) → Subdomain yang berbeza masih mencetuskan CORS → **Bandingkan skema, hos,
dan port.**
- Menganggap POST dalam Allow-Methods sudah mencukupi → Pengepala bukan mudah tidak dibenarkan → **Periksa Origin,
Method, dan Headers secara bersama.**
- Menggunakan
Allow-Origin: *dengan kelayakan → Pelayar enggan mendedahkan respons → **Kembalikan
asal tepat yang tersenarai dibenarkan.**
- Memantulkan setiap Origin yang diminta → Mana-mana tapak boleh mendapat akses baca kepada respons berkelayakan → **Padankan
dengan senarai dibenarkan yang tepat terlebih dahulu.**
- Mengesahkan OPTIONS sebagai pengguna yang telah log masuk → Preflight biasanya tidak mempunyai kelayakan → **Lakukan
pemeriksaan preflight yang terhad sebelum pengesahan pengguna.**
- Menggunakan
mode: "no-cors"sebagai pembaikan → Frontend menerima respons legap (opaque) yang tidak boleh dibaca → **Baiki
protokol CORS pelayan.**
- Menganggap CORS sebagai perlindungan CSRF → Permintaan boleh sampai ke pelayan dan menyebabkan kesan sampingan → **Kekalkan pemeriksaan CSRF
dan kebenaran pada operasi penulisan sensitif.**
- Menambah pengepala CORS hanya pada respons 2xx → Respons 401 atau 500 disamarkan sebagai ralat rentas asal → **Gunakan
pengepala CORS yang konsisten pada ralat untuk asal yang dibenarkan.**
- Menguji hanya dengan curl → Curl tidak menguatkuasakan dasar sama asal pelayar → **Sahkan dengan bukti
rangkaian pelayar yang sebenar.**
Soalan Susulan
Mengapakah permintaan boleh melangkau OPTIONS dan masih gagal CORS?
Permintaan yang menggunakan kaedah, pengepala, dan Content-Type yang tersenarai selamat boleh dihantar secara terus. Pelayar masih memeriksa pengepala CORS bagi respons sebenar. Jika perkongsian respons tidak dibenarkan, JavaScript tidak boleh membacanya walaupun pelayan mungkin telah memprosesnya. Bukti rangkaian harus membezakan antara "dihantar tetapi tidak dikongsi" daripada preflight yang gagal.
Mengapakah Access-Control-Allow-Credentials tidak patut muncul dalam permintaan preflight?
Ia merupakan pengepala respons pelayan yang menunjukkan bahawa pelayar boleh mendedahkan respons sebenar yang dibuat dalam mod kelayakan. Klien menyatakan niatnya melalui mod kelayakan Fetch; preflight itu sendiri tidak mengandungi kelayakan pengguna. Respons sebenar juga mesti memenuhi syarat asal khusus dan Allow-Credentials.
Apakah masalah yang diselesaikan oleh Vary: Origin?
Apabila pelayan memilih Access-Control-Allow-Origin berdasarkan Origin permintaan, cache kongsi mesti menyertakan Origin dalam kunci cachenya. Jika tidak, respons untuk satu asal yang dibenarkan boleh diguna semula untuk asal yang lain, menyebabkan penolakan atau pendedahan yang tidak betul.
Mengapakah kuki masih tiada selepas CORS dibaiki?
Periksa credentials: "include", kemudian periksa Domain, Path, Secure, SameSite, tarikh luput kuki, dan sekatan privasi pelayar. Kebenaran CORS dan penghantaran kuki adalah pintu kawalan yang bersebelahan tetapi bebas antara satu sama lain.
Bagaimanakah pelbagai domain frontend yang sah harus disokong?
Kekalkan senarai dibenarkan yang tepat, sahkan Origin permintaan yang lengkap, tulis semula nilai yang dipadankan, dan hantar Vary: Origin. Jangan kembalikan berbilang nilai Allow-Origin atau menggunakan ungkapan nalar (regular expression) yang longgar, padanan akhiran, atau pemantulan tanpa syarat.
Bolehkah frontend membaiki kegagalan CORS pelayan secara sendirian?
Tidak. Frontend boleh membuang pengepala bukan mudah yang tidak perlu atau memilih mod kelayakan yang dimaksudkan, tetapi API, proksi songsang (reverse proxy), atau get laluan mesti mengembalikan pengepala respons yang membenarkan perkongsian rentas asal. Kod pengeluaran tidak boleh bergantung pada sambungan pelayar (extensions) atau menyahdayakan kawalan keselamatan.
Berapa lamakah preflight harus dicache?
Pilih tempoh masa berdasarkan kekerapan perubahan konfigurasi, keperluan pembatalan, dan had pelayar. Semasa mengeluarkan perubahan CORS, uji kedua-dua cache sejuk dan laluan cache sedia ada. Penggunaan cache mengurangkan trafik OPTIONS tetapi boleh memanjangkan tempoh kebenaran yang buruk, jadi ia tidak boleh menyembunyikan kecacatan konfigurasi.