آشنایی با CORS و مفاهیم اصلی آن

CORS and its Main Concepts

03 اسفند 1399
آشنایی با CORS و مفاهیم اصلی آن

اشتراک منابع به صورت متقابل (CORS)

یک صفحه وب ممکن است آزادانه تصاویر، شیوه نامه ها، اسکریپت ها، iframe ها و ویدیوها را با منشا متقابل جاسازی کند. برخی از درخواست‌های بین دامنه‌ای، به‌ویژه درخواست‌های Ajax، به‌طور پیش‌فرض توسط خط ‌مشی امنیتی همان origin ممنوع هستند. CORS روشی را تعریف می‌کند که در آن مرورگر و سرور می‌توانند برای تعیین این که آیا اجازه دادن به درخواست cross-origin ایمن است یا خیر، تعامل داشته باشند. این امکان آزادی و عملکرد بیشتری را نسبت به درخواست‌های کاملا یکسان فراهم می‌کند، اما امن‌تر از اجازه دادن به همه درخواست‌های cross-origin است.

CORS یا اشتراک منابع به صورت متقابل یک سازوکار مبتنی بر HTTP-header است که به سرور اجازه می‌دهد هر origins (دامنه، scheme یا پورت) را که مرورگر باید از طریق آن اجازه بارگیری منابع به جز منبع خود را بدهد، نشان دهد. هم چنین به سازوکاری متکی است که به وسیله آن مرورگرها یک درخواست «preflight» را به سرور میزبان منبع متقابل می دهند تا بررسی کنند که آیا سرور درخواست واقعی را مجاز می کند یا نه. در این درخواست «preflight»، مرورگر header هایی را می فرستد که نشان دهنده متد HTTP  و header هایی است که در درخواست واقعی از آن استفاده می شود.

نمونه ای از یک درخواست متقابل:

کد جاوا اسکریپت frontend استفاده شده درhttps://domain-a.com از XMLHttpRequest برای فرستادن درخواست از https://domain-b.com/data.json  استفاده می کند.

به دلایل امنیتی، مرورگرها درخواست‌های HTTP که به صورت cross-origin (منابع متقابل) هستند و در اسکریپت‌ها قرار دارند را، محدود می‌کنند. برای مثال، XMLHttpRequest و Fetch API از خط مشی به نام same-origin policy پیروی می کنند. این به این معنی است که یک برنامه وب با استفاده از  آن  APIها فقط می تواند منابع را از همان مبدایی که برنامه بارگیری شده است درخواست کند، مگر این که پاسخ از origin های دیگر شامل هدرهای CORS مناسب باشد.

قوانین cors
قوانین cors

سازوکار CORS از درخواست های متقابل (cross-origin) ایمن و انتقال داده ها بین مرورگرها و سرورها پشتیبانی می کند. مرورگرهای مدرن از CORS در API هایی مانند XMLHttpRequest یا Fetch برای کاهش خطرات درخواست های HTTP که به صورت cross-origin هستند استفاده می کنند.

چه کسانی باید این مقاله را بخوانند؟

همه! به طور خاص، این مقاله برای مدیران وب، توسعه دهندگان سرور و توسعه دهندگان فرانت اند مناسب است. مرورگرهای مدرن، سمت کلاینت اشتراک‌گذاری crossp-origin، از جمله header ها و اجرای خط‌ مشی را مدیریت می‌کنند. اما استاندارد CORS به این معنی است که سرورها باید هدرهای request و response جدید را مدیریت کنند.

چه درخواست هایی از CORS استفاده می کنند؟

این استاندارد اشتراک‌ گذاری متقابل (cross-origin) می‌تواند درخواست‌های HTTP متقابل (cross-origin) را برای موارد زیر فعال کند:

  • فراخوانی های XMLHttpRequest یا Fetch API، همان طور که در بالا مورد بحث قرار گرفت.
  • فونت های وب (برای استفاده از فونت های متقابل cross-origin در @font-face در CSS)، به طوری که سرورها می توانند فونت های TrueType را که فقط می توانند به صورت cross-origin دانلود شوند و توسط وب سایت هایی که مجاز به انجام آن هستند، استفاده کنند.
  • بافت های WebGL
  • تصاویر/فریم های ویدئویی که با استفاده از ()drawImage روی بوم کشیده شده اند.
  • شکل های CSS به دست آمده از تصاویر.

بازخوانی کارکرد

مسیر XMLHttpRequest (XHR) از طریق CORS
مسیر XMLHttpRequest (XHR) از طریق CORS

استاندارد اشتراک‌گذاری منابع به صورت Cross-Origin با افزودن هدرهای HTTP کار می‌کند و به سرورها اجازه می‌دهد توضیح دهند، کدام منبع مجاز به خواندن آن اطلاعات از یک مرورگر وب هستند. علاوه بر این، برای متد‌های درخواست HTTP که می‌توانند اثرات جانبی بر روی داده‌های سرور ایجاد کنند (به ویژه، متدهای HTTP غیر از GET یا POST با انواع MIME خاص)، این مشخصات ایجاب می‌کند که مرورگرها درخواست را preflight کنند و متد‌های پشتیبانی شده را از سرور درخواست کنند. با متد درخواست HTTP OPTIONS، و سپس، پس از تایید از سرور، درخواست واقعی را فرستاده می شود. سرورها هم چنین می‌توانند به کلاینت ها اطلاع دهند که آیا اعتبارنامه‌ها (یعنی credentials مانند کوکی‌ها و احراز هویت HTTP)  باید همراه با درخواست‌ها فرستاده شوند.

شکست CORS منجر به خطا می شود، اما به دلایل امنیتی، جزئیات مربوط به خطا برای جاوا اسکریپت در دسترس نیست. تنها چیزی که کد می داند این است که یک خطا رخ داده است. تنها راه برای تعیین اینکه دقیقا چه اشتباهی رخ داده است، نگاه کردن به کنسول مرورگر برای جزئیات بیش تراست.بخش‌های بعدی راهبردها را مورد بحث قرار می‌دهند، و همچنین تفکیک هدرهای HTTP مورد استفاده را ارائه می‌کنند.

نمونه هایی از راهبردهای کنترل دسترسی

در این جا سه راهبرد را معرفی می‌کنیم که نشان می‌دهند چگونه اشتراک‌گذاری cross-origin کار می‌کند. همه این مثال‌ها از XMLHttpRequest استفاده می‌کنند که می‌تواند درخواست‌های cross-origin را در هر مرورگر پشتیبانی‌کننده ایجاد کنند.

const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';

xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();

درخواست های ساده

برخی از درخواست ها CORS preflight  را راه اندازی نمی کنند. به آن ها درخواست‌های ساده می‌گویند، اگرچه مشخصات Fetch (که CORS را تعریف می‌کند) از این اصطلاح استفاده نمی‌کند. یک درخواست ساده درخواستی است که تمام شرایط زیر را داشته باشد:

  • یکی از متد های مجاز زیر را داشته باشد:
  • به غیر از هدرهایی که به طور خودکار توسط عامل کاربر تنظیم می شوند (مثلا Connection، User-Agent یا سایر هدرهایی که در مشخصات Fetch به عنوان نام هدر ممنوعه تعریف شده اند)، تنها هدرهایی هستند که مجاز به تنظیم دستی هستند. مشخصات Fetch به عنوان یک هدر request ایمن توسط CORS تعریف می شود که عبارتند از:
  • تنها ترکیب‌های type/subtype مجاز برای media type مشخص‌شده در هدر Content-Type عبارتند از:
    • application/x-www-form-urlencoded
    • multipart/form-data
    • text/plain
  • اگر request با استفاده از یک شی XMLHttpRequest انجام شود، هیچ شنونده رویدادی روی شی ای که توسط ویژگی upload استفاده شده در درخواست بازگردانده شده ثبت نمی شود. یعنی، با توجه به یک نمونه xhr از XMLHttpRequest، هیچ کدی به نام xhr.upload.addEventListener()  برای اضافه کردن شنونده رویداد برای نظارت بر آپلود فراخوانی نشده است.
  • هیچ شی ReadableStream در درخواست استفاده نشده است.

توجه: WebKit Nightly و پیش‌نمایش فناوری Safari محدودیت‌های بیشتری را روی مقادیر مجاز در سرصفحه‌های Accept، Accept-Language و Content-Language اعمال می‌کنند. اگر هر یک از آن هدرها دارای مقادیر «nonstandard» باشند، WebKit/Safari درخواست را یک «درخواست ساده» نمی‌داند. این که چه مقادیری WebKit/Safari را «غیر استاندارد» در نظر می گیرند، به جز اشکالاتی که WebKit در زیر آمده اند، هنوز مستند نشده اند:

  • برای هدرهای request غیراستاندارد ایمن‌شده با CORS، AcceptAccept-Language, و  Content-Language الزامی هستند.
  • اجازه استفاده از کاما در هدرهای درخواست Accept، Accept-Language و Content-Language برای CORS ساده وجود دارد.
  • از مدل لیست سیاه برای هدرهای Accept محدود در درخواست‌های CORS استفاده کنید.

هیچ مرورگر دیگری این محدودیت های اضافی را اعمال نمی کند زیرا آن ها بخشی از مشخصات نیستند. برای مثال، فرض کنید محتوای وب در https://foo.example می‌خواهد محتوایی را در دامنه https://bar.other فراخوانی کند. کدی جاوا اسکریپت آن ممکن است مثل زیر باشد:

const xhr = new XMLHttpRequest();
const url = 'https://bar.other/resources/public-data/';

xhr.open('GET', url);
xhr.onreadystatechange = someHandler;
xhr.send();

این عملیات با استفاده از هدرهای CORS برای مدیریت امتیازات، تبادل ساده ای را بین کلاینت و سرور انجام می دهد:

تبادل بین کلاینت و سرور
تبادل بین کلاینت و سرور

بیایید ببینیم مرورگر در این مورد چه چیزی را برای سرور می فرستد و ببینیم سرور چگونه پاسخ می دهد:

GET /resources/public-data/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example

هدر note از request ،Origin است که نشان می دهد فراخوانی از این آدرس https://foo.example شده است.

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 00:23:53 GMT
Server: Apache/2
Access-Control-Allow-Origin: *
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Transfer-Encoding: chunked
Content-Type: application/xml

[…XML Data…]

در پاسخ، سرور یک هدر Access-Control-Allow-Origin را با Access-Control-Allow-Origin: * برمی گرداند که به این معنی است که منبع می تواند توسط هر منبعی قابل دسترسی باشد.

Access-Control-Allow-Origin: *

این الگوی هدرهای Origin و Access-Control-Allow-Origin ساده ترین استفاده از پروتکل کنترل دسترسی است. اگر صاحبان منبع در https://bar.other مایلند دسترسی به منبع را فقط به درخواست های https://foo.example محدود کنند، (یعنی هیچ دامنه ای به جز https://foo.example نمی تواند به منبع با روش cross-origin دستیابی داشته باشد) آن گاه خط زیر را ارسال می کنند:

Access-Control-Allow-Origin: https://foo.example

توجه: هنگام پاسخ به درخواست credentialed requests (درخواست های اعتبارسنجی)، سرور باید به جای تعیین علامت "*"، باید یک مبدا در مقدار هدر Access-Control-Allow-Origin مشخص کند.

درخواست های preflighted

برخلاف درخواست‌های ساده، برای درخواست‌های «preflighted»، مرورگر ابتدا یک درخواست HTTP را با استفاده از متد OPTIONS به منبعی در مبدا دیگر ارسال می‌کند تا تعیین کند آیا درخواست واقعی برای ارسال امن است یا خیر. چنین درخواست‌هایی با cross-origin به صورت preflighted انجام می‌شوند زیرا ممکن است پیامدهایی برای داده‌های کاربر داشته باشند.در زیر نمونه ای از درخواستی است که از قبل ارسال می شود:

const xhr = new XMLHttpRequest();
xhr.open('POST', 'https://bar.other/resources/post-here/');
xhr.setRequestHeader('X-PINGOTHER', 'pingpong');
xhr.setRequestHeader('Content-Type', 'application/xml');
xhr.onreadystatechange = handler;
xhr.send('<person><name>Arun</name></person>');

مثال بالا یک بدنه XML برای ارسال با درخواست POST ایجاد می کند. هم چنین، یک هدر درخواست غیراستاندارد X-PINGOTHER تنظیم شده است. چنین هدرهایی بخشی از HTTP/1.1 نیستند، اما به طور کلی برای برنامه های کاربردی وب مفید هستند. از آن جایی که درخواست از یک Content-Type of application/xml استفاده می کند، و از آن جایی که یک هدر سفارشی تنظیم شده است، این درخواست از preflighted می شود.

درخواست های preflighted
درخواست های preflighted

توجه: همانطور که در زیر توضیح داده شد، درخواست POST واقعی شامل هدرهای *-Access-Control-Request نمی شود. آن ها فقط برای درخواست OPTIONS مورد نیاز هستند. بیایید به تبادل کامل بین کلاینت و سرور نگاه کنیم. اولین تبادل درخواست/پاسخ preflighte است:

OPTIONS /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Origin: https://foo.example
Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

HTTP/1.1 204 No Content
Date: Mon, 01 Dec 2008 01:15:39 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400
Vary: Accept-Encoding, Origin
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive

خطوط 1 - 10 بالا درخواست preflight  را با متد OPTIONS نشان می دهد. مرورگر تشخیص می دهد که باید این را بر اساس پارامترهای درخواستی که قطعه کد جاوا اسکریپت در بالا استفاده می کرد ارسال کند تا سرور بتواند پاسخ دهد که آیا ارسال درخواست با پارامترهای درخواست قابل قبول است یا خیر. OPTIONS یک متد HTTP/1.1 است که برای تعیین اطلاعات از سرورها استفاده می شود و روشی امن است، به این معنی که نمی توان از آن برای تغییر منبع استفاده کرد. توجه داشته باشید که همراه با درخواست OPTIONS، دو header درخواست دیگر ارسال می شود (به ترتیب خطوط 9 و 10):

Access-Control-Request-Method: POST
Access-Control-Request-Headers: X-PINGOTHER, Content-Type

هدر Access-Control-Request-Method به عنوان بخشی از یک درخواست preflighte به سرور اطلاع می دهد که وقتی درخواست ارسال می شود، این کار را با روش درخواست POST انجام می دهد. هدر Access-Control-Request-* به سرور اطلاع می دهد که وقتی درخواست واقعی ارسال شد، این کار را با هدرهای سفارشی X-PINGOTHER و Content-Type انجام می دهد. اکنون سرور این فرصت را دارد تا تعیین کند که آیا تحت این شرایط می تواند درخواستی را بپذیرد یا خیر.خطوط 13 - 22 در بالا پاسخی است که سرور ارائه می دهد که نشان می دهد متد درخواست POST و هدر درخواست  X-PINGOTHER  هستند. بیایید نگاهی دقیق تر به خطوط 16-19 بیندازیم:

Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Methods: POST, GET, OPTIONS
Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
Access-Control-Max-Age: 86400

سرور با Access-Control-Allow-Origin پاسخ می دهد، https://foo.example، فقط دسترسی به دامنه اصلی درخواست کننده را محدود می کند. همچنین با Access-Control-Allow-Methods پاسخ می دهد، که می گوید POST و GET متد های معتبری برای کوئری کردن از منبع مورد نظر هستند (این هدر شبیه به هدر پاسخ Allow است، اما دقیقا در چارچوب کنترل دسترسی استفاده می شود). سرور هم چنین Access-Control-Allow-Headers را با مقدار "X-PINGOTHER، Content-Type"  ارسال می کند و تایید می کند که این هدر ها مجاز هستند تا با درخواست واقعی استفاده شوند. مانند Access-Control-Allow-Methods ،Access-Control-Allow-Headers که لیستی از هدرهای قابل قبول جدا شده با کاما هستند.سرانجام، Access-Control-Max-Age مدت زمانی که پاسخ به درخواست preflight  بدون ارسال درخواست preflight  دیگر در حافظه پنهان می شود را در چند ثانیه نشان می دهد. مقدار پیش فرض 5 ثانیه است. در مورد حاضر، حداکثر سن 86400 ثانیه (= 24 ساعت) است. توجه داشته باشید که هر مرورگر دارای حداکثر مقدار داخلی است که وقتی Access-Control-Max-Age از آن بیش تر شود اولویت دارد.پس از تکمیل درخواست preflight ، درخواست واقعی ارسال می شود:

POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache

<person><name>Arun</name></person>

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some XML payload]
  1. 1. یک درخواست ساده (با استفاده از url برای Fetch API، یا XMLHttpRequest.responseURL ) برای تعیین این که درخواست preflight واقعی به کدام URL ختم می شود، ارسال می کند.
  2. با استفاده از آدرس اینترنتی که از Response.url یا XMLHttpRequest.responseURL در مرحله اول دریافت کردید، درخواست دیگری (درخواست واقعی) می دهد.

درخواست‌ها و تغییر مسیرهای preflight

همه مرورگرها در حال حاضر از تغییر مسیرهای زیر پس از یک درخواست preflight پشتیبانی نمی کنند. اگر پس از چنین درخواستی تغییر مسیری رخ دهد، برخی از مرورگرها در حال حاضر پیام خطایی مانند زیر را گزارش می کنند:این درخواست بهhttps://example.com/foo  هدایت می شود، که برای درخواست‌های cross-origin که نیاز به preflight اولیه دارند، مجاز نیست. درخواست نیاز به  preflight دارد که به دنبال تغییر مسیرهای cross-origin مجاز نیست.پروتکل CORS در ابتدا به این رفتار نیاز داشت اما متعاقبا تغییر کرد تا دیگر نیازی به آن نباشد. با این حال، همه مرورگرها این تغییر را اعمال نکرده اند، و بنابراین همچنان رفتار مورد نیاز اولیه را نشان می دهند.تا زمانی که مرورگرها به مشخصات فنی دسترسی پیدا نکنند، ممکن است بتوانید با انجام یک یا هر دو مورد زیر این محدودیت را برطرف کنید:

  • رفتار سمت سرور را تغییر دهید تا از preflight یا از تغییر مسیر جلوگیری کنید.
  • درخواست را طوری تغییر دهید که یک درخواست ساده باشد که باعث preflight نشود.

اگر این کار امکان پذیر نیست، راه دیگری این است که:

  • یک درخواست ساده با استفاده از url برای Fetch API یا XMLHttpRequest.responseURL برای تعیین این که درخواست واقعی از preflight به کدام URL ختم می شود، ارسال کنید.
  • با استفاده از آدرس اینترنتی که از url یا XMLHttpRequest.responseURL در مرحله اول دریافت کردید، درخواست دیگری (درخواست واقعی) بدهید.

با این حال، اگر درخواستی است که به دلیل وجود هدر مجوز در درخواست، preflight را راه‌اندازی می‌کند، نمی‌توانید با استفاده از مراحل بالا، محدودیت را برطرف کنید. و شما نمی توانید به هیچ وجه روی آن کار کنید مگر این که روی سروری که درخواست به آن داده می شود کنترل داشته باشید.

درخواست هایی با اعتبار (credential)

توجه: هنگام ارسال درخواست‌های اعتبارسنجی به دامنه دیگری، سیاست‌های کوکی شخص ثالث همچنان اعمال می‌شود. این خط‌ مشی همیشه بدون توجه به تنظیماتی که در این فصل توضیح داده شده در سرور و کلاینت اجرا می‌شود. جالب‌ترین قابلیتی که هم توسط XMLHttpRequest یا Fetch و CORS در معرض دید قرار می‌گیرد، توانایی ایجاد درخواست‌های «معتبر» است که از کوکی‌های HTTP و اطلاعات احراز هویت HTTP آگاه هستند. به‌طور پیش‌فرض، در فراخوان‌های XMLHttpRequest یا Fetch، مرورگرها اعتبارنامه ارسال نمی‌کنند. هنگام فراخوانی، یک فلگ خاص باید روی شی XMLHttpRequest یا سازنده Request تنظیم شود.در این مثال، محتوایی که در ابتدا از https://foo.example بارگیری شده است، یک درخواست GET ساده به منبعی در https://bar.other که کوکی ها را تنظیم می کند، می دهد. محتوای foo.example ممکن است حاوی جاوا اسکریپت مانند این باشد:

POST /doc HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
X-PINGOTHER: pingpong
Content-Type: text/xml; charset=UTF-8
Referer: https://foo.example/examples/preflightInvocation.html
Content-Length: 55
Origin: https://foo.example
Pragma: no-cache
Cache-Control: no-cache

<person><name>Arun</name></person>

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:15:40 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 235
Keep-Alive: timeout=2, max=99
Connection: Keep-Alive
Content-Type: text/plain

[Some XML payload]

خط 7 فلگ XMLHttpRequest را نشان می‌دهد که باید برای فراخوانی با کوکی‌ها تنظیم شود، یعنی برای مقدار بولی withCredentials . به طور پیش فرض، فراخوانی بدون کوکی انجام می شود. از آن جایی که این یک درخواست ساده GET است، از preflight داده نمی شود، اما مرورگر هر پاسخی را که هدر واقعی Access-Control-Allow-Credentials ندارد را رد می کند و پاسخ را در دسترس محتوای وب فراخوانی قرار نمی دهد.

درخواست های preflighted
درخواست های preflighted

در این جا یک نمونه تبادل بین مشتری و سرور وجود دارد:

GET /resources/credentialed-content/ HTTP/1.1
Host: bar.other
User-Agent: Mozilla/5.0 (Macintosh; Intel Mac OS X 10.14; rv:71.0) Gecko/20100101 Firefox/71.0
Accept: text/html,application/xhtml+xml,application/xml;q=0.9,*/*;q=0.8
Accept-Language: en-us,en;q=0.5
Accept-Encoding: gzip,deflate
Connection: keep-alive
Referer: https://foo.example/examples/credential.html
Origin: https://foo.example
Cookie: pageAccess=2

HTTP/1.1 200 OK
Date: Mon, 01 Dec 2008 01:34:52 GMT
Server: Apache/2
Access-Control-Allow-Origin: https://foo.example
Access-Control-Allow-Credentials: true
Cache-Control: no-cache
Pragma: no-cache
Set-Cookie: pageAccess=3; expires=Wed, 31-Dec-2008 01:34:53 GMT
Vary: Accept-Encoding, Origin
Content-Encoding: gzip
Content-Length: 106
Keep-Alive: timeout=2, max=100
Connection: Keep-Alive
Content-Type: text/plain

[text/plain payload]

اگرچه خط 10 حاوی کوکی است که برای محتوای https://bar.other در نظر گرفته شده است، اگر bar.other با Access-Control-Allow-Credentials پاسخ نمی دهد (خط 17)، پاسخ نادیده گرفته می شود و در دسترس محتوای وب قرار نمی گیرد.

درخواست ها و credentialهای (اعتبارنامه های) Preflight

درخواست های CORS، Preflight هرگز نباید شامل credential باشند. پاسخ به یک درخواست Preflight باید Access-Control-Allow-Credentials: true  را مشخص کند تا نشان دهد که درخواست واقعی می تواند با credential ها انجام شود.

توجه: برخی از سرویس‌های Authentication سازمانی بر خلاف مشخصات Fetch، مستلزم این هستند که گواهی‌های مشتری TLS در درخواست‌های preflight ارسال شوند.فایرفاکس 87 اجازه می دهد تا این رفتار ناسازگار با تنظیم اولویت network.cors_preflight.allow_client_cert با true  bug 1511151 فعال شود. مرورگرهای مبتنی بر Chromium در حال حاضر همیشه گواهی های سرویس گیرنده TLS را در درخواست های پیش از پرواز CORS ارسال می کنند. (Chrome bug 775438)

درخواست‌های Credentialed  و علامت ها

هنگام پاسخ به یک درخواست معتبر:

  • سرور نباید علامت "*" را برای مقدار response-header  (پاسخ-هدر) Access-Control-Allow-Origin مشخص کند، بلکه باید یک مبدا (origin) صریح را مشخص کند. به عنوان مثال: Access-Control-Allow-Origin: https://example.com
  • سرور نباید علامت "*" را برای مقدارresponse-header (پاسخ-هدر) Access-Control-Allow-Headers  مشخص کند، بلکه باید لیست صریحی از نام های هدر را مشخص کند. برای مثال، Access-Control-Allow-Headers: X-PINGOTHER, Content-Type
  • سرور نباید علامت "*" را برای مقدارresponse-header  (پاسخ-هدر) Access-Control-Allow-Methods مشخص کند، بلکه باید یک لیست صریح از نام متد ها را مشخص کند. به عنوان مثال، Access-Control-Allow-Methods: POST, GET

اگر یک درخواست شامل یک credential  (معمولا یک هدر کوکی) باشد و پاسخ شامل یک هدر Access-Control-Allow-Origin: *  باشد، مرورگر دسترسی به پاسخ را مسدود کرده و یک خطای CORS را در کنسول devtools گزارش می‌کند. اما اگر یک درخواست شامل یک credential  (مانند هدر کوکی) باشد و پاسخ شامل یک منبع واقعی به جای یک علامت باشد (Access-Control-Allow-Origin: https://example.com)، پس مرورگر اجازه دسترسی به پاسخ از مبدا مشخص شده را می دهد.

کوکی های Third-party

توجه داشته باشید که کوکی‌های تنظیم‌شده در پاسخ‌های CORS پیرو خط ‌مشی‌های کوکی Third-party هستند. در مثال بالا، صفحه از foo.example بارگیری می شود اما کوکی در خط 20 توسط bar.other ارسال می شود و بنابراین اگر مرورگر کاربر به گونه ای پیکربندی شده باشد که همه کوکی های شخص ثالث را رد کند، ذخیره نمی شود. کوکی در درخواست (خط 10) نیز ممکن است در خط مشی‌های عادی کوکی Third-party سرکوب شود. بنابراین، سیاست کوکی اجباری ممکن است قابلیت توضیح داده شده در این فصل را باطل کند و عملا مانع از ارائه درخواست‌های اعتباری شما شود.

هدرهای پاسخ HTTP

این بخش هدرهای response (پاسخ) HTTP را که سرورها برای درخواست‌های کنترل دسترسی برمی‌گردانند، همان طور که توسط مشخصات اشتراک‌گذاری Cross-Origin تعریف شده است، فهرست می‌کند. بخش قبل یک نمای کلی از این موارد در عمل ارائه می دهد.

Access-Control-Allow-Origin

یک منبع برگشتی ممکن است یک هدر Access-Control-Allow-Origin با دستور زیر داشته باشد:

Access-Control-Allow-Origin: <origin> | *

Access-Control-Allow-Origin یک مبد واحد را مشخص می کند که به مرورگرها می گوید به آن مبدا اجازه دسترسی به منبع را بدهند. یا در غیر این صورت - برای درخواست‌های بدون credential علامت "*" به مرورگرها می‌گوید که به هر منبعی اجازه دسترسی به منبع را بدهند. به عنوان مثال، برای اجازه دادن به کد از مبدا https://mozilla.org برای دسترسی به منبع، می توانید مشخص کنید:

Access-Control-Allow-Origin: https://mozilla.org
Vary: Origin

اگر سرور یک origin  یکتا را مشخص کند (که ممکن است به صورت پویا براساس origin  درخواست به عنوان بخشی از لیست مجاز تغییر کند) به جای علامت "*"، سرور باید origin  را نیز در هدر پاسخ Vary قرار دهد تا به مشتریان نشان دهد که سرور پاسخ می دهد. بر اساس مقدار هدر درخواست origin  متفاوت خواهد بود.

Access-Control-Expose-Headers

هدر Access-Control-Expose-Headers هدرهای مشخص شده را به لیست مجاز اضافه می کند که جاوا اسکریپت (مانند getResponseHeader) در مرورگرها اجازه دسترسی به آن را دارد.

Access-Control-Expose-Headers: <header-name>[, <header-name>]*

برای نمونه داریم:

Access-Control-Expose-Headers: X-My-Custom-Header, X-Another-Custom-Header

به هدرهای X-My-Custom-Header و X-Another-Custom-Header اجازه می دهد تا در دسترس مرورگر قرار گیرند.

Access-Control-Max-Age

هدر Access-Control-Max-Age نشان می دهد که نتایج یک درخواست preflight چه مدت می تواند ذخیره شود. برای نمونه ای از درخواست preflight ، به نمونه های بالا مراجعه کنید.

Access-Control-Max-Age: <delta-seconds>

پارامتر delta-seconds نشان‌دهنده تعداد ثانیه‌هایی است که نتایج می‌توانند در حافظه پنهان شوند.

Access-Control-Allow-Credentials

هدر Access-Control-Allow-Credentials نشان می دهد که آیا پاسخ به درخواست، زمانی که پرچم Credential دارای مقدار true است قابل نمایش است یا خیر. هنگامی که به عنوان بخشی از پاسخ به درخواست preflight استفاده می شود، نشان می دهد که آیا می توان درخواست واقعی را با استفاده از Credential ها انجام داد یا خیر. توجه داشته باشید که درخواست‌های ساده GET از preflight نمی‌شوند، و بنابراین اگر درخواستی برای منبعی با Credential ارسال شود، اگر این هدر همراه با منبع برگردانده نشود، پاسخ توسط مرورگر نادیده گرفته می‌شود و به محتوای وب بازگردانده نمی‌شود.

Access-Control-Allow-Credentials: true

 

Access-Control-Allow-Methods

هدر Access-Control-Allow-Methods متد یا متدهای مجاز را هنگام دسترسی به منبع مشخص می کند. این در پاسخ به درخواست preflight استفاده می شود. شرایطی که تحت آن یک درخواست از preflight  می شود در بالا مورد بحث قرار گرفته است.

Access-Control-Allow-Methods: <method>[, <method>]*

نمونه ای از درخواست preflight  در بالا آورده شده است، از جمله مثالی که این هدر را به مرورگر ارسال می کند.

Access-Control-Allow-Headers

هدر Access-Control-Allow-Headers در پاسخ به یک درخواست preflight استفاده می شود تا نشان دهد که از کدام هدرهای HTTP می توان هنگام درخواست واقعی استفاده کرد. این هدر پاسخ سمت سرور به هدر Access-Control-Request-Headers مرورگر است.

Access-Control-Allow-Headers: <header-name>[, <header-name>]*

هدرهای درخواست HTTP

این بخش هدرهایی را فهرست می‌کند که کلاینت ها ممکن است هنگام صدور درخواست‌های HTTP به منظور استفاده از ویژگی اشتراک‌گذاری cross-origin از آن استفاده کنند. توجه داشته باشید که این هدرها برای شما در هنگام فراخوانی سرورها تنظیم شده است. توسعه دهندگانی که از قابلیت XMLHttpRequest به صورت cross-origin استفاده می کنند، مجبور نیستند هدر درخواست اشتراک گذاری cross-origin را به صورت برنامه ریزی شده تنظیم کنند.

origin (مبدا)

هدر Origin مبدا درخواست دسترسی cross-origin یا درخواست preflight  را نشان می دهد.

Origin: <origin>

Origin یک URL است که نشان دهنده سروری است که درخواست از آن آغاز شده است. این شامل هیچ اطلاعات مسیر نیست، فقط نام سرور را شامل می شود.

توجه: مقدار Origin می تواند null باشد.

توجه داشته باشید که در هر درخواست کنترل دسترسی، هدر Origin همیشه ارسال می شود.

Access-Control-Request-Method

متد Access-Control-Request-Method هنگام صدور یک درخواست preflight  استفاده می شود تا به سرور اطلاع دهد که در زمان انجام درخواست واقعی از کدام متد HTTP استفاده می شود.

Access-Control-Request-Method: <method>

Access-Control-Request-Headers

هدر Access-Control-Request-Headers هنگام صدور یک درخواست preflight استفاده می شود تا به سرور اطلاع دهد که هنگام انجام درخواست واقعی از چه هدرهای HTTP استفاده می شود (مانند setRequestHeader) این هدر سمت مرورگر توسط هدر سمت سرور تکمیلی Access-Control-Allow-Headers پاسخ داده می شود.

Access-Control-Request-Headers: <field-name>[, <field-name>]*

هدرهای request و response به طور خلاصه:

هدرهای request (درخواست)

  • Origin
  • Access-Control-Request-Method
  • Access-Control-Request-Headers

هدرهای response (پاسخ)

  • Access-Control-Allow-Origin
  • Access-Control-Allow-Credentials
  • Access-Control-Expose-Headers
  • Access-Control-Max-Age
  • Access-Control-Allow-Methods
  • Access-Control-Allow-Headers

مشکل CORS چیست؟

اگر شما نیز از توسعه دهندگان وب باشید با مشکلات CORS آشنا شده اید. در صورتی که با این مفهوم آشنا نیستید حتما مطالعه این مقاله را به شما پیشنهاد می کنم. ما در این مقاله مباحث ابتدایی HTTP را توضیح نخواهیم داد بنابراین داشتن درک صحیح و ساده از عملیات های HTTP برای هضم این  مقاله لازم است.

زمانی که ما در حال توسعه front-end هستیم در اکثر اوقات داده ای را نمایش می دهیم که جای دیگری (روی سرور دیگری) قرار دارد. به همین منظور مرورگر باید ابتدا یک درخواست HTTP را به سرور مقصد ارسال کند تا تمام داده های مورد نیاز ما را دریافت کند. ما در اینجا نام فرضی www.mywebsite.com  را برای وب سایت خود در نظر می گیریم. آدرس API این وب سایت را نیز api.website.com  در نظر می گیریم. زمانی که درخواستی به این API ارسال می شود، داده های ما به صورت JSON برای کاربر (هر کسی که درخواست را ارسال کرده است) برگردانده خواهند شد. ما در حال حاضر روی سایت www.mywebsite.com هستیم اما اگر این بار درخواست خود را به دامنه دیگری مانند www.anotherdomain.com ارسال کنیم چه می شود؟ با اجرای این درخواست خطایی به شکل زیر دریافت می کنیم:

Access to fetched has been blocked by CORS policy

CORS مخفف Cross-origin resource sharing یا به اشتراک گذاری منابع از چند سورس مختلف است. برای درک بهتر CORS باید با Same-Origin Policy شروع کنیم.

Same-Origin Policy چیست؟

مرورگرها و دنیای وب به طوری طراحی شده اند که قانونی به نام same-origin policy را اجرا می کنند. این قانون می گوید مقصد درخواست های ما باید با منبعی که به آن هستیم یکی باشد. زمانی مقصد درخواست و منبع آن یکی نیست که:

  • به یک دامنه یا زیردامنه دیگر متصل شویم.
  • به یک پروتکل دیگر متصل شویم.
  • به یک پورت دیگر متصل شویم.

به طور مثال تا زمانی که به وب سایت mywebsite.com متصل باشیم هیچ اشکالی ندارد که به منابع آن دسترسی داشته باشیم (مثلا https://mywebsite.com/image1.png) اما نمی توانیم به منابع وب سایت دیگری مانند anotherdomain.com دسترسی داشته باشیم. این مسئله به سادگی در تصویر زیر مشخص است:

origin (منابع) های مختلف در CORS
origin (منابع) های مختلف در CORS

شاید از خودتان بپرسید چرا قانون same-origin وجود دارد؟ فرض کنید هکری لینک مخربی را برای شما ارسال کرده باشد. شما با کلیک روی این لینک به وب سایت هکر منتقل می شوید (مثلا www.evilwebsite.com) که در ظاهر وب سایت بدی نیست اما در پشت صحنه یک iframe از وب سایت بانکی شما (مثلا www.bank.com) باز می شود و هکر با استفاده از کوکی هایی که در مرورگر شما است، شما را وارد حسابتان می کند. طبیعتا این مسئله یک رخنه امنیتی بزرگ است و نباید مجاز باشد. قانون same-origin برای جلوگیری از چنین حملاتی ایجاد شده است و می گوید که دسترسی به resource ها باید از همان origin ای باشد که به آن متصل هستیم. با وجود این قانون وب سایت www.evilwebsite.com نمی تواند به www.bank.com دسترسی داشته باشد.

CORS در سمت کلاینت

منظور ما از سمت کلاینت همان front-end یا مرورگر است. زمانی که وارد دنیای مرورگرها و front-end می شویم باید بدانید که قانون same-origin policy فقط مربوط به اسکریپت ها می شود اما مرورگر ها آن را بسط دادند تا شامل درخواست های جاوا اسکریپتی نیز بشود بنابراین به صورت پیش فرض ما فقط می توانیم به منابعی دسترسی داشته باشیم که از same-origin (از سایتی که در آن هستیم) باشند:

رد شدن درخواست CORS
رد شدن درخواست CORS

مسئله اینجاست که ما در مواقع بسیاری نیاز به برقراری ارتباط با origin های دیگر (وب سایت های دیگر) داریم. آیا راهی وجود دارد که بر اساس آن بتوانیم به شکل امن از منابع سایت های دیگر نیز استفاده کنیم؟ بله CORS همان راه حل ما است. برایتان توضیح دادم که CORS مخفف Cross-origin resource sharing یا به اشتراک گذاری منابع از چند سورس مختلف است و از همین نام متوجه می شویم که کار آن چیست. User agent ها (مثلا یک مرورگر یا کتابخانه axios یا هر agent دیگری که از آن برای اتصال به یک سایت استفاده شده است) می توانند از مکانیسم خاصی به نام CORS استفاده کنند تا نوع خاصی از درخواست های cross-origin را ارسال کنند. این عملیات با استفاده از تنظیم header های خاصی در درخواست HTTP شما انجام می شود.

زمانی که می خواهیم یک درخواست Cross-origin (درخواستی به منبع دیگر) را ارسال کنیم، مرورگر شما یک header خاص به نام Origin را به درخواست HTTP اضافه می کند. مقدار این header برابر با منبعی است که درخواست از آن ارسال شده است. سرور این درخواست را دریافت کرده و header هایش را بررسی می کند تا بداند هر درخواست از کجا آمده است.

CORS در سمت سرور

اگر شما توسعه دهنده سرور هستید باید header های مجموعه Access-Control را به پاسخ های سرور خود اضافه کنید. مرورگرها بر اساس مقدار این دسته از header ها می توانند مشخص کنند که چه نوع درخواست های cross-origin ای مجاز هستند. در صورتی که Access-Control در header شما نباشد، تمام درخواست های cross-origin بلوکه خواهند شد. تعداد header های مربوط به Access-Control بسیار زیاد است اما مرورگر فقط یک header خاص را برای مجاز دانستن درخواست های cross-origin می خواهد و آن هم Access-Control-Allow-Origin می باشد. مقداری که به این header می دهید مشخص خواهد کرد که چه مقصد هایی می توانند به این منبع (سرور ما) درخواست ارسال کنند. به طور مثال اگر قرار است وب سایت https://mywebsite.com به سرور و وب سایت ما (مثلا https://api.mywebsite.com) دسترسی داشته باشد باید آن را به header ذکر شده پاس بدهیم:

هدر access control در درخواست های CORS
هدر access control در درخواست های CORS

حالا اگر درخواستی از سمت https://mywebsite.com ارسال شود می تواند به منابع موجود در https://api.mywebsite.com نیز دسترسی داشته باشد. از این به بعد مرورگر مقدار موجود در Access-Control-Allow-Origin را با مقصد درخواست شما مقایسه می کند و اگر این دو یکی نباشند اجازه ارسال درخواست را نخواهید داشت. شما می توانید این دو حالت را در دو تصویر زیر مشاهده کنید:

درخواست صحیح CORS
درخواست صحیح CORS
درخواست غلط CORS
درخواست غلط CORS

در تصویر دوم خطای معروف CORS را مشاهده می کنیم. قسمت دوم این درخواست دقیقا چیزی که ما توضیح دادیم را ذکر می کند:

The 'Access-Control-Allow-Origin' header has a value

 'https://www.mywebsite.com' that is not equal

to the supplied origin.

در نظر داشته باشید که Access-Control-Allow-Origin می تواند مقدار * را نیز بگیرد که در این صورت تمام درخواست های CORS از هر منبعی مجاز خواهند بود. انجام این کار پیشنهاد نمی شود چرا که خطرات امنیتی خاص خودش را دارد.


منبع: وب‌سایت‌های mozilla و dev.to

نویسنده شوید

دیدگاه‌های شما (2 دیدگاه)

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

سمانه
21 تیر 1400
عالی بود . ممنون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
25 دی 1400
سپاس از همراهیتون

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

jawad
13 خرداد 1400
عالی بود

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.

روکسو
25 دی 1400
خوشحالیم که مورد پسند شما بود.

در این قسمت، به پرسش‌های تخصصی شما درباره‌ی محتوای مقاله پاسخ داده نمی‌شود. سوالات خود را اینجا بپرسید.