С CORS получилось почти как в анекдоте: "Я долго не понимал, а потом привык".
А сейчас припёрло и срочно пришлось понимать, что это. Не буду повторяться, оставлю три ссылки:

Практически классический документ:

Видео, разъясняющее нюансы с точки зрения протокола:

И резюме...

... интересное кратким пересказом по заголовкам:
- Access-Control-Allow-Origin: Which origin is allowed?
- Access-Control-Allow-Credentials: Are requests allowed even if the credentials mode is set to include?
- Access-Control-Allow-Headers: Which headers may be used?
- Access-Control-Allow-Methods: Which HTTP request methods are allowed?
- Access-Control-Expose-Headers: Which headers may be displayed?
- Access-Control-Max-Age: How old may the preflight request be before it expires?
- Access-Control-Request-Headers: Which HTTP header is specified in the preflight request?
- Access-Control-Request-Method: Which HTTP method is specified in the preflight request?
- Origin: What is the source of the request?
Ах да, разумеется, следует не забыть упомянуть

Содержащий множество примеров серверных конфигов для CORS. Но меня интересует только конфиг для nginx:
#
# Wide-open CORS config for nginx
#
location / {
if ($request_method = 'OPTIONS') {
add_header 'Access-Control-Allow-Origin' '*';
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
#
# Custom headers and headers various browsers *should* be OK with but aren't
#
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
#
# Tell client that this pre-flight info is valid for 20 days
#
add_header 'Access-Control-Max-Age' 1728000;
add_header 'Content-Type' 'text/plain; charset=utf-8';
add_header 'Content-Length' 0;
return 204;
}
if ($request_method = 'POST') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
if ($request_method = 'GET') {
add_header 'Access-Control-Allow-Origin' '*' always;
add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS' always;
add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range' always;
add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range' always;
}
}
Для сайта на PHP есть нюанс: правила надо прописывать не в корневом локейшене, а в location ~ \.php$
Есть подвох: заголовок Access-Control-Allow-Origin работает по принципу "все или один" и принимает либо *
, либо конкретное указание домена со схемой (обязательно!) и портом (опционально). И что же нам делать, если мы хотим разрешить хосту отвечать "да" на кросс-доменный запрос из нескольких разных источников?
Есть лайфхак - и для апача (смотрите на SO), и для nginx:
location ~ \.php$ {
if ( $http_origin ~* (https?://(.+\.)?(wintersky|imaginaria)\.(?:ru|me)$) ) {
set $cors "$http_origin";
}
if ($request_method = 'GET') {
add_header "Access-Control-Allow-Origin" "$cors";
....
}
# аналогично делаем для блоков POST и OPTIONS, если необходимо
}
... и он работает.
P.S. А сколько я пытался разобраться в этом раньше? Нет, пока не упёрся в реальную необходимость внедрения - понимания не возникло. Нет, не отрицаю, полного понимания нет и сейчас, но я хотя бы понял принцип!