فهم أخطاء CORS وإصلاحها: دليل عملي لـ Express وSpring Boot وNginx
تنشر واجهة أمامية جديدة، تفتح وحدة التحكم (console)، فتجد خطأ CORS أحمر يخبرك بأن الطلب «تم حظره بواسطة سياسة CORS». تعمل واجهة برمجة التطبيقات (API) في Apidog أو curl، لكن المتصفح يرفض تسليم الاستجابة إلى JavaScript. السبب غالبًا بسيط: الخادم لم يرسل رؤوس Access-Control-Allow-Origin الصحيحة.
الحقيقة المهمة: يفرض المتصفح CORS، لكن الخادم هو سبب الخطأ. لذلك يكون الإصلاح عادةً في تكوين الخادم، لا في كود الواجهة الأمامية.
في هذا الدليل ستتعلم:
- كيف يعمل CORS وطلب الفحص المسبق (preflight).
- كيفية إصلاح أخطاء CORS الستة الأكثر شيوعًا.
- تكوين CORS في Express وSpring Boot وNginx.
- تصحيح الأخطاء خارج المتصفح باستخدام Apidog.
للتفاصيل الرسمية، راجع وثائق MDN حول CORS ومواصفات Fetch.
ما هو CORS؟
CORS هو اختصار لـ Cross-Origin Resource Sharing، أي مشاركة الموارد عبر المصادر.
تفرض المتصفحات افتراضيًا سياسة نفس المصدر (same-origin policy). لذلك لا يمكن لـ JavaScript يعمل على:
https://app.example.com
قراءة استجابة من:
https://api.example.com
لأن المخطط (scheme) أو المضيف (host) أو المنفذ (port) مختلف.
تذكّر النقاط التالية:
-
المتصفح هو من يفرض CORS: طلبات الخادم إلى الخادم و
curlوعملاء API لسطح المكتب لا يطبقون هذه الفحوصات. - الخادم هو من يكوّن CORS: يقرر المتصفح السماح بناءً على رؤوس الاستجابة.
- الطلب قد يصل إلى الخادم رغم الخطأ: في الطلبات البسيطة قد يعالج الخادم الطلب ويرسل الاستجابة، ثم يمنع المتصفح JavaScript من قراءتها.
- CORS ليس جدارًا أمنيًا للـ API: هو يحمي المستخدمين من صفحات خبيثة تحاول قراءة بياناتهم عبر المصادر باستخدام ملفات تعريف الارتباط الخاصة بهم.
عند ظهور خطأ CORS، افحص رسالة الخطأ وأصلح الرأس المفقود أو الخاطئ على الخادم بدل البحث عن حل التفافي في الواجهة الأمامية.
تشريح طلب الفحص المسبق
قبل بعض الطلبات عبر المصادر، يرسل المتصفح طلب OPTIONS استكشافيًا يسمى الفحص المسبق (preflight).
يُرسل الفحص المسبق عادةً عندما:
- تستخدم طريقة غير
GETأوHEADأوPOST. - ترسل رؤوسًا مخصصة مثل
Authorization. - تستخدم
Content-Type: application/json.
مثال:
OPTIONS /v1/orders HTTP/1.1
Host: api.example.com
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
إجابة الخادم المتوقعة:
HTTP/1.1 204 No Content
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Methods: GET, POST, PUT, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type
Access-Control-Max-Age: 86400
Vary: Origin
إذا كان أي جزء مطلوب مفقودًا، يلغي المتصفح الطلب الفعلي قبل تشغيله. قد ترى طلب OPTIONS في السجلات، لكن نقطة نهاية API لن تُستدعى.
يحدد Access-Control-Max-Age مدة تخزين نتيجة الفحص المسبق مؤقتًا بالثواني. في المثال، سيخزن المتصفح القرار لمدة 86400 ثانية.
السؤال الأساسي عند تصحيح CORS هو:
هل فشل الفحص المسبق، أم فشل الطلب الفعلي؟
أخطاء CORS الستة الأكثر شيوعًا
1. عدم وجود رأس Access-Control-Allow-Origin
أرسل الخادم استجابة بلا رؤوس CORS، فلم يجد المتصفح ما يقيّمه.
أرسل مصدرًا محددًا:
Access-Control-Allow-Origin: https://app.example.com
أو استخدم * فقط مع واجهات API عامة لا تستخدم بيانات اعتماد.
احرص على إضافة الرؤوس إلى كل الاستجابات، بما فيها 401 و403 و500. إذا أضافت Middleware رؤوس CORS إلى استجابات النجاح فقط، فقد يعرض المتصفح خطأ CORS بدل خطأ الخادم الحقيقي. راجع أيضًا شرح حالة 403 Forbidden.
2. استخدام * مع بيانات الاعتماد
يحدث ذلك عندما ترسل الواجهة الأمامية:
fetch(url, { credentials: 'include' });
بينما يرد الخادم بـ:
Access-Control-Allow-Origin: *
هذا الاقتران غير مسموح به. استخدم المصدر الفعلي وأضف:
Access-Control-Allow-Origin: https://app.example.com
Access-Control-Allow-Credentials: true
تحقق من قيمة Origin الواردة مقابل قائمة مصادر مسموح بها، ولا تعكس مصادر عشوائية مع تفعيل بيانات الاعتماد.
3. فشل الفحص المسبق في اختبار التحكم بالوصول
قد لا يتعامل الخادم مع OPTIONS، فيعيد 404 أو 405. وقد ترفضه طبقة المصادقة بـ401 لأن الفحص المسبق لا يحمل رمز المصادقة؛ فالمتصفح لا يرسل بيانات الاعتماد مع طلبات الفحص المسبق.
عالج OPTIONS قبل تشغيل المصادقة:
app.options('/v1/orders', (req, res) => {
res.set({
'Access-Control-Allow-Origin': 'https://app.example.com',
'Access-Control-Allow-Methods': 'GET, POST, PUT, DELETE, OPTIONS',
'Access-Control-Allow-Headers': 'Authorization, Content-Type'
});
res.sendStatus(204);
});
في معظم الأطر، يكفي تركيب Middleware الخاص بـ CORS قبل Middleware المصادقة.
4. عدم تطابق قيمة الرأس مع المصدر
قد يرسل الخادم رأس Access-Control-Allow-Origin، لكنه يحدد مصدرًا مختلفًا. من الأسباب الشائعة:
- اختبار
http://localhost:5173بينما الخادم يسمح فقط بمصدر الإنتاج. - اختلاف
httpوhttps. - اختلاف المنفذ.
- وجود شرطة مائلة زائدة؛ فـ
https://app.example.com/ليست قيمة مصدر صالحة.
استخدم قائمة مصادر صريحة وأعد المصدر المطابق:
const allowed = [
'https://app.example.com',
'http://localhost:5173'
];
if (allowed.includes(req.headers.origin)) {
res.set('Access-Control-Allow-Origin', req.headers.origin);
res.set('Vary', 'Origin');
}
يساعد Vary: Origin ذاكرة التخزين المؤقت وشبكات CDN على عدم تقديم استجابة مصدر إلى مصدر آخر.
5. عدم السماح بطريقة أو رأس مطلوب
قد تظهر رسائل مثل:
Request header field authorization is not allowed by Access-Control-Allow-Headers
أو:
Method PUT is not allowed by Access-Control-Allow-Methods
وسّع استجابة الفحص المسبق لتشمل كل ما ترسله الواجهة الأمامية:
Access-Control-Allow-Methods: GET, POST, PUT, PATCH, DELETE, OPTIONS
Access-Control-Allow-Headers: Authorization, Content-Type, X-Request-Id
أسماء الرؤوس غير حساسة لحالة الأحرف، أما أسماء الطرق فهي حساسة لحالة الأحرف ويُفضّل كتابتها بأحرف كبيرة.
6. إعادة توجيه طلب الفحص المسبق
إذا أعاد عنوان الفحص المسبق رمز 301 أو 302، فقد يرفض المتصفح متابعة إعادة التوجيه.
الأسباب المعتادة:
- استخدام عنوان
httpيعيد التوجيه إلىhttps. - إضافة أو حذف شرطة مائلة زائدة في المسار.
- إعادة توجيه بوابة
/v1/ordersإلى/v1/orders/.
الإصلاح:
- استخدم عنوان
httpsالنهائي مباشرة. - طابق اتفاقية الشرطة المائلة الخاصة بالموجه.
- اختبر طلب
OPTIONSوتأكد من حصوله على2xxبدل3xx.
أمثلة تكوين الخادم
Express
استخدم حزمة CORS الرسمية بدل كتابة الرؤوس يدويًا:
const express = require('express');
const cors = require('cors');
const app = express();
app.use(cors({
origin: ['https://app.example.com', 'http://localhost:5173'],
methods: ['GET', 'POST', 'PUT', 'DELETE'],
allowedHeaders: ['Authorization', 'Content-Type'],
credentials: true,
maxAge: 86400
}));
ركّبها قبل Middleware المصادقة حتى لا ترفض الفحوصات المسبقة بسبب غياب الرموز.
في تطبيقات Flask، يوفر امتداد Flask-CORS نمطًا مشابهًا.
Spring Boot
يمكنك استخدام WebMvcConfigurer:
@Configuration
public class CorsConfig implements WebMvcConfigurer {
@Override
public void addCorsMappings(CorsRegistry registry) {
registry.addMapping("/v1/**")
.allowedOrigins("https://app.example.com")
.allowedMethods("GET", "POST", "PUT", "DELETE")
.allowedHeaders("Authorization", "Content-Type")
.allowCredentials(true)
.maxAge(86400);
}
}
إذا كنت تستخدم Spring Security، أضف:
.cors(Customizer.withDefaults())
إلى سلسلة فلاتر الأمان. وإلا قد تمنع طبقة الأمان الفحص المسبق قبل وصوله إلى إعدادات MVC. راجع وثائق Spring CORS للتفاصيل.
Nginx
إذا كان Nginx أمام التطبيق، يمكنك معالجة الفحص المسبق عند الحافة:
location /v1/ {
if ($request_method = OPTIONS) {
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Access-Control-Allow-Methods "GET, POST, PUT, DELETE, OPTIONS" always;
add_header Access-Control-Allow-Headers "Authorization, Content-Type" always;
add_header Access-Control-Max-Age 86400 always;
return 204;
}
add_header Access-Control-Allow-Origin "https://app.example.com" always;
add_header Vary "Origin" always;
proxy_pass http://backend;
}
كلمة always مهمة؛ فمن دونها لا يضيف Nginx الرؤوس إلى استجابات 4xx و5xx.
اختر طبقة واحدة لإدارة CORS. إذا أضاف كل من Nginx والتطبيق الرؤوس، فقد تحصل على قيم مكررة مثل:
Access-Control-Allow-Origin: *, *
ويرفض المتصفح الاستجابة.
تصحيح أخطاء CORS باستخدام Apidog
يخبرك خطأ وحدة التحكم بأن المتصفح حظر الاستجابة، لكنه لا يعرض دائمًا ما أرسله الخادم. يزيل Apidog المتصفح من المعادلة، لأن طلباته لا تخضع لفحوصات CORS الخاصة بالمتصفح.
إذا نجح الطلب في Apidog، فغالبًا منطق API سليم والمشكلة في رؤوس CORS. وإذا فشل هناك أيضًا، فالمشكلة خطأ عادي في API وليست CORS فقط. راجع دليل تقنيات اختبار API.
خطوات التصحيح
-
أعد إنشاء الطلب الحقيقي: انسخ الطلب الفاشل من Network في المتصفح إلى Apidog، مع الطريقة والرؤوس والجسم. افحص الحالة والجسم؛ فرمز
500يعني أن لديك خطأ خادم حقيقي. -
اختبر الفحص المسبق يدويًا: أنشئ طلب
OPTIONSوأضف:
Origin: https://app.example.com
Access-Control-Request-Method: POST
Access-Control-Request-Headers: authorization, content-type
- افحص الاستجابة: ابحث عن:
Access-Control-Allow-Origin
Access-Control-Allow-Methods
Access-Control-Allow-Headers
-
تحقق من الإصلاح: بعد تعديل إعدادات الخادم، أعد إرسال طلب
OPTIONSالمحفوظ وتأكد من تحديث الرؤوس.
هذه الطريقة تحسم بسرعة مشكلة «يعمل في عميل API ويفشل في المتصفح». يعمل العميل لأنه يتجاوز CORS، بينما يحتاج المتصفح إلى موافقة الخادم عبر الرؤوس الصحيحة. يمكنك تنزيل Apidog والاحتفاظ بطلب OPTIONS بجوار اختبارات API العادية.
قائمة تحقق CORS في 30 ثانية
قبل فتح بلاغ، تحقق من التالي:
- هل تحتوي الاستجابة الفاشلة على
Access-Control-Allow-Origin؟ - هل تتطابق القيمة تمامًا مع مصدر الصفحة من حيث المخطط والمضيف والمنفذ؟
- هل توجد شرطة مائلة زائدة؟
- عند استخدام ملفات تعريف الارتباط أو المصادقة، هل يوجد مصدر محدد و
Access-Control-Allow-Credentials: trueبدل*؟ - هل يعيد
OPTIONSرمز2xx؟ - هل تغطي
Access-Control-Allow-MethodsوAccess-Control-Allow-Headersطلبك؟ - هل توجد إعادة توجيه على عنوان URL الخاص بالفحص المسبق؟
- هل تحتوي استجابات
401و403و500على رؤوس CORS نفسها الموجودة في استجابات النجاح؟
في معظم الحالات، سيكشف طلب OPTIONS يدوي السبب مباشرة: رأس مفقود، أو مصدر غير صحيح، أو إعادة توجيه، أو طريقة غير مسموح بها.
الأسئلة الشائعة
لماذا يظهر خطأ CORS في المتصفح فقط؟
لأن المتصفحات هي التي تفرض CORS وسياسة نفس المصدر. تتحقق من Access-Control-Allow-Origin في الاستجابات عبر المصادر، بينما لا يطبق curl وخدمات الواجهة الخلفية وعملاء سطح المكتب القاعدة نفسها.
إذا نجح الطلب في كل مكان باستثناء المتصفح، فغالبًا تفتقد استجابة الخادم رؤوس CORS أو تحتوي على إعدادات غير صحيحة.
هل ينطبق CORS على Postman أو Apidog؟
لا. Postman وApidog تطبيقان لسطح المكتب، وليسا صفحات ويب داخل بيئة المتصفح المعزولة. لذلك تتجاوز طلباتهما CORS وتعرضان رؤوس الاستجابة الأصلية للخادم.
نجاح الطلب في عميل سطح المكتب لا يثبت أن المتصفح سيتمكن من قراءته، لكنه يساعدك على عزل طبقة المشكلة. راجع أيضًا اختبار Postman CORS.
هل خطأ CORS ميزة أمنية أم خطأ برمجي؟
هو سلوك أمني مقصود. يمنع المتصفح النصوص البرمجية من كشف بيانات الاستجابة عبر المصادر ما لم يوافق الخادم.
تعطيل CORS عبر أعلام المتصفح أو الإضافات يخفي المشكلة على جهازك فقط، ولا يصلحها للمستخدمين الآخرين. أصلح رؤوس الخادم بدلًا من ذلك.
هل يمكنني استخدام Access-Control-Allow-Origin: * في كل مكان؟
استخدمه فقط مع واجهات API عامة للقراءة لا تعتمد على ملفات تعريف الارتباط أو المصادقة.
عند استخدام بيانات الاعتماد:
- استخدم قائمة مصادر مسموح بها.
- أعد المصدر المطابق بدل
*. - أرسل
Access-Control-Allow-Credentials: true. - أضف
Vary: Originللحفاظ على فصل الاستجابات في ذاكرة التخزين المؤقت المشتركة.
Top comments (0)