جافا سكريبت - كيف يعمل ترويسة 'Access-Control-Allow-Origin'؟
من الواضح أنني قد أسأت فهم دلالاته تمامًا. كنت أفكر في شيء مثل هذا:
- يقوم العميل بتحميل رمز JavaScript المسمى MyCode.js من
http://siteA- الأصل. - يحتوي رأس الاستجابة لملف MyCode.js على Access-Control-Allow-Origin:
http://siteB، والذي ظننت أنه يعني أن MyCode.js مسموح له بإجراء مراجع عبر الأصل إلى الموقع B. - يقوم العميل بتشغيل بعض وظائف MyCode.js، والتي بدورها تقوم بعمل طلبات إلى
http://siteB، والذي يجب أن يكون جيدًا، على الرغم من أنها طلبات من مصدر مختلف.
حسنًا، أنا مخطئ. لا يعمل الأمر هكذا على الإطلاق. لذا، قرأت مشاركة الموارد بين المصادر وحاولت قراءة مشاركة الموارد بين المصادر في توصية w3c.
شيء واحد مؤكد - ما زلت لا أفهم كيف يُفترض بي استخدام هذا العنوان.
لدي سيطرة كاملة على كل من الموقع A والموقع B. كيف يمكنني تمكين كود جافا سكريبت الذي تم تحميله من الموقع A للوصول إلى الموارد على الموقع B باستخدام هذا العنوان؟
ملاحظة: أنا لا أريد استخدام JSONP.
الإجابة رقم 1
Access-Control-Allow-Origin هو رأس CORS (مشاركة الموارد عبر الأصول).
عندما يحاول الموقع A جلب المحتوى من الموقع B، يمكن للموقع B إرسال رأس استجابة Access-Control-Allow-Origin لإخبار المتصفح بأن محتوى هذه الصفحة متاح لأصول معينة. (الأصل origin هو نطاق، بالإضافة إلى المخطط ورقم المنفذ.) بشكل افتراضي، صفحات الموقع B غير متاحة لأي أصل آخر؛ استخدام رأس Access-Control-Allow-Origin يفتح بابًا للوصول عبر الأصول من الأصول الطالبة المحددة.
لكل مورد/صفحة ترغب الموقع B في جعلها متاحة للموقع A، يجب على الموقع B تقديم صفحاته مع رأس الاستجابة:
Access-Control-Allow-Origin: http://siteA.com
المتصفحات الحديثة لن تمنع تمامًا طلبات المصادر المتقاطعة. إذا طلب الموقع أ صفحة من الموقع ب، فإن المتصفح سيقوم فعليًا بجلب الصفحة المطلوبة على مستوى الشبكة والتحقق مما إذا كانت رؤوس الاستجابة تسرد الموقع أ كمجال مصرح له بالطلب. إذا لم يشِر الموقع ب إلى أن الموقع أ مسموح له بالوصول إلى هذه الصفحة، فإن المتصفح سيُطلق حدث خطأ لـ XMLHttpRequest ويرفض بيانات الاستجابة للشفرة الجافا سكريبت التي قامت بالطلب.
طلبات غير بسيطة
ما يحدث على مستوى الشبكة يمكن أن يكون أكثر تعقيدًا قليلًا مما تم شرحه أعلاه. إذا كان الطلب "غير بسيط"، فإن المتصفح يرسل أولاً طلب OPTIONS "تجريبي" بدون بيانات، للتحقق من أن الخادم سيقبل الطلب. يكون الطلب غير بسيط عندما يكون أحد الشرطين (أو كلاهما) مستخدمًا:
- فعل HTTP بخلاف GET أو POST (مثل PUT، DELETE);
رؤوس الطلبات غير البسيطة؛ رؤوس الطلبات البسيطة الوحيدة هي:
قبول;
اللغة المقبولة;لغة-المحتوى;نوع المحتوى(هذا يكون بسيطًا فقط عندما تكون قيمتهapplication/x-www-form-urlencodedأوmultipart/form-dataأوtext/plain).
إذا استجاب الخادم لطلب الخيارات المسبق (OPTIONS preflight) برؤوس استجابة مناسبة (Access-Control-Allow-Headers للرؤوس غير البسيطة، Access-Control-Allow-Methods للأفعال غير البسيطة) التي تتطابق مع الفعل غير البسيط و/أو الرؤوس غير البسيطة، فإن المتصفح يرسل الطلب الفعلي.
بافتراض أن الموقع A يريد إرسال طلب PUT إلى /somePage، مع قيمة Content-Type غير بسيطة وهي application/json، فإن المتصفح سيرسل أولاً طلبًا مسبقًا:
OPTIONS /somePage HTTP/1.1
Origin: http://siteA.com
Access-Control-Request-Method: PUT
Access-Control-Request-Headers: Content-Type
لاحظ أن Access-Control-Request-Method و Access-Control-Request-Headers يتم إضافتهما تلقائيًا بواسطة المتصفح؛ لا تحتاج إلى إضافتهما. يحصل هذا الطلب التمهيدي OPTIONS على رؤوس الاستجابة الناجحة:
Access-Control-Allow-Origin: http://siteA.com
Access-Control-Allow-Methods: GET, POST, PUT
Access-Control-Allow-Headers: Content-Type
عند إرسال الطلب الفعلي (بعد إكمال الاختبار المسبق)، يكون السلوك مطابقًا لكيفية التعامل مع الطلب البسيط. بعبارة أخرى، يُعامل الطلب غير البسيط الذي نجح اختباره المسبق بنفس طريقة الطلب البسيط (أي، يجب على الخادم إرسال Access-Control-Allow-Origin مرة أخرى للاستجابة الفعلية).
يُرسل المتصفح الطلب الفعلي:
PUT /somePage HTTP/1.1
Origin: http://siteA.com
Content-Type: application/json
{ "myRequestContent": "JSON is so great" }
وإرسال الخادم مرة أخرى Access-Control-Allow-Origin، تمامًا كما يفعل مع الطلب البسيط:
Access-Control-Allow-Origin: http://siteA.com
انظر فهم XMLHttpRequest عبر CORS للحصول على مزيد من المعلومات حول الطلبات غير البسيطة.
الإجابة رقم 2
مشاركة الموارد عبر المصادر المختلفة - CORS (المعروف أيضاً باسم طلب AJAX عبر النطاقات) هي مشكلة قد يواجهها معظم مطوري الويب، وفقاً لسياسة المصدر نفسه، تقيد المتصفحات جافاسكريبت العميل في بيئة أمان، عادةً لا يمكن لجافاسكريبت الاتصال مباشرة بخادم بعيد من نطاق مختلف. في الماضي، أنشأ المطورون العديد من الطرق المعقدة لتحقيق طلب الموارد عبر النطاقات، وأكثر الطرق شيوعًا هي:
- استخدم فلاش/سيلفرلايت أو الجانب الخادم كـ "وكيل" للتواصل مع البعيد.
- JSON مع الحشو (JSONP).
- يضيف خادم بعيد في إطار iframe ويتواصل من خلال المقاطع أو window.name، راجع هنا.
تلك الطرق المراوغة لديها أكثر أو أقل بعض المشاكل، على سبيل المثال قد يؤدي JSONP إلى ثغرة أمنية إذا قام المطورون ببساطة بـ "eval" له، و#3 أعلاه، على الرغم من أنه يعمل، يجب على كلا النطاقين بناء عقد صارم بين بعضهما البعض، فهو ليس مرنًا ولا أنيقًا حسب رأيي :)
قدمت W3C مشاركة الموارد عبر الأصول (CORS) كحل قياسي لتوفير طريقة آمنة ومرنة ومُوصى بها لحل هذه المشكلة.
الآلية
من منظور عام يمكننا ببساطة اعتبار CORS كعقد بين استدعاء AJAX من العميل من النطاق A وصفحة مستضافة على النطاق B، وسيكون الطلب/الاستجابة المتبادل عبر المصادر نموذجياً كما يلي:
رؤوس طلبات AJAX الخاصة بـ DomainA
Host DomainB.com
User-Agent Mozilla/5.0 (Windows NT 6.1; WOW64; rv:2.0) Gecko/20100101 Firefox/4.0
Accept text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8,application/json
Accept-Language en-us;
Accept-Encoding gzip, deflate
Keep-Alive 115
Origin http://DomainA.com
رؤوس استجابة DomainB
Cache-Control private
Content-Type application/json; charset=utf-8
Access-Control-Allow-Origin DomainA.com
Content-Length 87
Proxy-Connection Keep-Alive
Connection Keep-Alive
الأجزاء الزرقاء التي قمت بتحديدها أعلاه كانت الحقائق الأساسية، رأس الطلب "Origin" "يشير إلى مصدر الطلب عبر الأصول أو طلب التحقق المسبق"، رأس الاستجابة "Access-Control-Allow-Origin" يشير إلى أن هذه الصفحة تسمح بالطلبات البعيدة من DomainA (إذا كانت القيمة * فإنها تشير إلى السماح بالطلبات البعيدة من أي نطاق).
كما ذكرت أعلاه، أوصت W3 المتصفح بتنفيذ "طلب ما قبل الرحلة" قبل إرسال طلب HTTP عبر الأصل في الواقع، وباختصار، هو طلب HTTP OPTIONS:
OPTIONS DomainB.com/foo.aspx HTTP/1.1
إذا كانت صفحة foo.aspx تدعم فعل HTTP OPTIONS، فقد تعيد استجابة مثل الموضح أدناه:
HTTP/1.1 200 OK
Date: Wed, 01 Mar 2011 15:38:19 GMT
Access-Control-Allow-Origin: http://DomainA.com
Access-Control-Allow-Methods: POST, GET, OPTIONS, HEAD
Access-Control-Allow-Headers: X-Requested-With
Access-Control-Max-Age: 1728000
Connection: Keep-Alive
Content-Type: application/json
فقط إذا كان الرد يحتوي على "Access-Control-Allow-Origin" وقيمته "*" أو تحتوي على النطاق الذي قدم طلب CORS، من خلال تلبية هذا الشرط الإلزامي سيقوم المتصفح بإرسال طلب عبر النطاق الفعلي، وتخزين النتيجة في "Preflight-Result-Cache".
الإجابة رقم 3
وفقًا لهذه المقالة على شبكة مطوري موزيلا,
يقوم المورد بعمل طلب HTTP عبر الأصل عندما يطلب موردًا من نطاق أو منفذ مختلف عن النطاق أو المنفذ الذي يخدم منه المورد الأول نفسه.
صفحة HTML مستضافة من http://domain-a.com تقوم بطلب مصدر <img> من http://domain-b.com/image.jpg. العديد من الصفحات على الويب اليوم تقوم بتحميل موارد مثل CSS style sheets والصور والسكريبتات من نطاقات منفصلة (لذلك يجب أن يكون ذلك طبيعيًا).
سياسة الأصل نفسه
لأسباب أمنية، تقوم المتصفحات بتقييد طلبات HTTP عبر النطاقات التي تبدأ من داخل السكربتات. على سبيل المثال، XMLHttpRequest و Fetch يتبعان سياسة نفس الأصل. لذلك، يمكن لتطبيق ويب يستخدم XMLHttpRequest أو Fetch فقط إجراء طلبات HTTP إلى نطاقه الخاص.
مشاركة الموارد عبر المصادر (CORS)
لتحسين تطبيقات الويب، طلب المطوّرون من بائعي المتصفحات السماح بالطلبات عبر النطاقات.
آلية مشاركة الموارد عبر الأصول (CORS) تمنح خوادم الويب ضوابط الوصول عبر النطاقات، التي تُمكّن من نقل البيانات عبر النطاقات بشكل آمن. تستخدم المتصفحات الحديثة CORS داخل حاوية API مثل XMLHttpRequest أو fetch للتقليل من مخاطر طلبات HTTP عبر الأصول.
كيف يعمل CORS (رأس Access-Control-Allow-Origin)
ويكيبيديا:
يصف معيار CORS رؤوس HTTP الجديدة التي توفر للمتصفحات والخوادم وسيلة لطلب عناوين URL البعيدة فقط عندما يكون لديهم إذن.
على الرغم من أن بعض عمليات التحقق والتفويض يمكن أن يتم تنفيذها بواسطة الخادم، فالخيار العام مسؤولية المتصفح لدعم هذه الرؤوس واحترام القيود التي يفرضونها.
مثال
- يقوم المتصفح بإرسال طلب
OPTIONSمع رأسOrigin HTTP.قيمة هذا الرأس هي النطاق الذي قدم الصفحة الأصلية. عندما تحاول صفحة من
http://www.example.comالوصول إلى بيانات مستخدم فيservice.example.com، سيتم إرسال رأس الطلب التالي إلىservice.example.com:lang-none Origin: http://www.example.com
- قد يستجيب الخادم في
service.example.comبما يلي:
رأس
Access-Control-Allow-Origin(ACAO) في استجابته يوضح المواقع الأصلية المسموح بها. على سبيل المثال:Access-Control-Allow-Origin: http://www.example.comصفحة خطأ إذا لم يسمح الخادم بالطلب عبر الأصول المتعددة
رأس
Access-Control-Allow-Origin(ACAO) مع عامل بدل يسمح لجميع المجالات:Access-Control-Allow-Origin: *
الإجابة رقم 4
كلما بدأت أفكر في CORS، تكون حدسيتي حول أي موقع يستضيف الرؤوس غير صحيحة، تمامًا كما وصفت في سؤالك. بالنسبة لي، يساعد التفكير في غرض سياسة الأصل نفسه.
الغرض من سياسة الأصل نفسه هو حمايتك من جافاسكريبت الخبيثة على siteA.com من الوصول إلى المعلومات الخاصة التي اخترت مشاركتها فقط مع siteB.com. بدون سياسة الأصل نفسه، يمكن لجافاسكريبت التي كتبها مؤلفو siteA.com أن تجعل متصفحك يرسل طلبات إلى siteB.com مستخدمًا ملفات تعريف الارتباط الخاصة بالمصادقة لموقع siteB.com. وبهذه الطريقة، يمكن لـ siteA.com سرقة المعلومات السرية التي تشاركها مع siteB.com.
أحيانًا تحتاج إلى العمل عبر نطاقات مختلفة، وهنا يأتي دور CORS. يقوم CORS بتخفيف سياسة المصدر نفسه لموقع siteB.com، باستخدام العنوان Access-Control-Allow-Origin لتحديد النطاقات الأخرى (siteA.com) التي يُسمح لها بتشغيل JavaScript يمكنه التفاعل مع siteB.com.
لفهم أي نطاق يجب أن يخدم رؤوس CORS، اعتبر هذا. أنت تزور malicious.com، الذي يحتوي على بعض جافا سكريبت التي تحاول إجراء طلب عبر نطاق إلى mybank.com. يجب أن يكون الأمر متروكًا لـ mybank.com، وليس malicious.com، ليقرر ما إذا كان سيضع رؤوس CORS التي تخفف من سياسة المصدر نفسه، مما يسمح لجافا سكريبت من malicious.com بالتفاعل معه. إذا كان بإمكان malicious.com تحديد رؤوس CORS الخاصة به للسماح لجافا سكريبت الخاصة به بالوصول إلى mybank.com، فإن هذا سيبطل تمامًا سياسة المصدر نفسه.
أعتقد أن سبب حدسي السيئ هو وجهة النظر التي أمتلكها عند تطوير موقع. إنه موقعي، مع كل جافا سكريبتي. لذلك، فإنه لا يقوم بأي شيء خبيث، ويجب أن يكون الأمر متروكًا لـ لي لتحديد المواقع الأخرى التي يمكن لـ جافا سكريبتي التفاعل معها. في الحقيقة يجب أن أفكر: أي مواقع أخرى تحاول جافا سكريبت الخاصة بها التفاعل مع موقعي وهل يجب أن أستخدم CORS للسماح لهم؟
الإجابة رقم 5
من تجربتي الشخصية، من الصعب العثور على شرح بسيط لماذا تعتبر CORS مصدر قلق أصلاً.
بمجرد أن تفهم سبب وجوده، تصبح العناوين والمناقشة أكثر وضوحًا بكثير. سأحاول ذلك في بضعة أسطر.
الأمر كله يتعلق بالكوكيز. يتم تخزين الكوكيز على جهاز العميل حسب النطاق الخاص بها.
قصة مثال: على حاسوبك، هناك ملف تعريف ارتباط لموقع
yourbank.com. ربما تكون جلستك موجودة هناك.
النقطة الأساسية: عندما يقوم العميل بإرسال طلب إلى الخادم، سيقوم بإرسال ملفات تعريف الارتباط المخزنة تحت النطاق الخاص بهذا الطلب.
أنت مسجل الدخول على متصفحك إلى
yourbank.com. تطلب رؤية جميع حساباتك، ويتم إرسال ملفات تعريف الارتباط لـyourbank.com. يتلقىyourbank.comمجموعة ملفات تعريف الارتباط ويرسل ردّه مرة أخرى (حساباتك).
إذا قام عميل آخر بإجراء طلب عبر الأصل إلى خادم، يتم إرسال تلك الكوكيز معه، كما كان من قبل. روح روه.
أنت تتصفح
malicious.com. يقوم موقع Malicious بإرسال مجموعة من الطلبات إلى بنوك مختلفة، بما في ذلكyourbank.com.
نظرًا لأن ملفات تعريف الارتباط تم التحقق من صحتها كما هو متوقع، سيقوم الخادم بالموافقة على الاستجابة.
تُجمع تلك الكوكيز وتُرسل - والآن،
malicious.comلديه استجابة منyourbank.
يا إلهي.
لذا الآن، تتضح بعض الأسئلة والأجوبة:
«لماذا لا نمنع المتصفح من القيام بذلك فقط؟» نعم. هذا هو CORS.
كيف نتجاوز ذلك؟ اجعل الخادم يخبر الطلب أن CORS مسموح.
الإجابة رقم 6
باستخدام React و Axios، قم بربط رابط بروكسي بالرابط وأضف رأسًا كما هو موضح أدناه:
https://cors-anywhere.herokuapp.com/ + عنوان API الخاص بك
إضافة رابط الوكيل فقط ستعمل، لكنها قد تُظهر أيضًا خطأ عدم الوصول مرة أخرى. لذا من الأفضل إضافة رأس كما هو موضح أدناه.
axios.get(`https://cors-anywhere.herokuapp.com/[YOUR_API_URL]`,{headers: {'Access-Control-Allow-Origin': '*'}})
.then(response => console.log(response:data);
}
تحذير: لا يُستخدم في الإنتاج
هذا مجرد حل سريع. إذا كنت تواجه صعوبة في معرفة سبب عدم قدرتك على الحصول على رد، يمكنك استخدام هذا. ولكن مرة أخرى، هذا ليس الحل الأمثل للإنتاج.
الإجابة رقم 7
1. يقوم العميل بتحميل كود جافاسكريبت MyCode.js من http://siteA - المصدر.
الكود الذي يقوم بالتنزيل - سواء كان علامة سكربت HTML أو XHR من جافاسكريبت أو أي شيء آخر - جاء من، لنفترض، http://siteZ. وعندما يطلب المتصفح MyCode.js، فإنه يرسل رأس Origin يقول "Origin: http://siteZ"، لأنه يمكنه رؤية أنك تطلب إلى siteA و siteZ != siteA. (لا يمكنك إيقاف هذا أو التدخل فيه.)
2. يحتوي رأس الاستجابة لملف MyCode.js على Access-Control-Allow-Origin: http://siteB، والذي كنت أظن أنه يعني أن MyCode.js مسموح له بإجراء مراجع عبر الأصل إلى الموقع B.
لا. هذا يعني أن الموقع B فقط هو المسموح له بإجراء هذا الطلب. لذا طلبك لـ MyCode.js من الموقع Z سيواجه خطأ بدلاً من ذلك، والمتصفح عادةً لن يعطيك أي شيء. ولكن إذا جعلت خادمك يُرجع A-C-A-O: siteZ بدلاً من ذلك، ستحصل على MyCode.js. أو إذا أرسل '*'، فإن ذلك سيعمل، وسيتيح للجميع الوصول. أو إذا كان الخادم يرسل دائمًا السلسلة من رأس Origin: ... لكن... لأغراض الأمان، إذا كنت خائفًا من القراصنة، يجب على خادمك السماح فقط بالأصول الموجودة في قائمة قصيرة، المسموح لها بإجراء تلك الطلبات.
ثم، يأتي MyCode.js من siteA. عندما يقوم بإجراء طلبات إلى siteB، تكون جميعها عبر مصادر مختلفة، يرسل المتصفح Origin: siteA، وعلى siteB أن يأخذ siteA، ويتعرف على أنه موجود في القائمة القصيرة للمرسلين المسموح لهم، ويرسل مرة أخرى A-C-A-O: siteA. فقط عندها سيسمح المتصفح للسكريبت الخاص بك بالحصول على نتائج تلك الطلبات.
الإجابة رقم 8
لقد عملت مع Express.js 4 و Node.js 7.4 و Angular، وكان لدي نفس المشكلة. هذا ما ساعدني:
أ) جانب الخادم: في الملف app.js أضيف رؤوسًا إلى كل الردود، مثل:
app.use(function(req, res, next) {
res.header('Access-Control-Allow-Origin', req.headers.origin);
res.header("Access-Control-Allow-Headers", "Origin, X-Requested-With, Content-Type, Accept");
next();
});
يجب أن يكون هذا قبل جميع المسارات.
رأيت الكثير من هذه الرؤوس المضافة:
res.header("Access-Control-Allow-Headers","*");
res.header('Access-Control-Allow-Credentials', true);
res.header('Access-Control-Allow-Methods', 'GET,PUT,POST,DELETE');
لكنني لا أحتاج إلى ذلك
ب) جانب العميل: عند الإرسال بواسطة Ajax، تحتاج إلى إضافة "withCredentials: true," مثل:
$http({
method: 'POST',
url: 'url',
withCredentials: true,
data : {}
}).then(function(response){
// Code
}, function (response) {
// Code
});
الإجابة رقم 9
إذا كنت تستخدم PHP، حاول إضافة الكود التالي في بداية ملف الـ PHP:
إذا كنت تستخدم الخادم المحلي، جرب هذا:
header("Access-Control-Allow-Origin: *");
إذا كنت تستخدم نطاقات خارجية مثل السيرفر، جرب هذا:
header("Access-Control-Allow-Origin: http://www.website.com");
الإجابة رقم 10
إذا كنت تريد فقط اختبار تطبيق عبر المجالات حيث يقوم المتصفح بحظر طلبك، فيمكنك ببساطة فتح متصفحك في الوضع غير الآمن واختبار تطبيقك دون تغيير كودك ودون جعل كودك غير آمن.
من نظام macOS، يمكنك القيام بذلك من خلال سطر الأوامر في الطرفية:
open -a Google\ Chrome --args --disable-web-security --user-data-dir
