- PVSM.RU - https://www.pvsm.ru -
Если вы сталкивались с CORS, то знаете всю ту боль, которую испытывает разработчик, когда нужно сходить к API на другом домене. Если конфигурация сервера не доступна для настройки, то использовали какое-нибудь решение на основе не менее популярного решения cors-anywhere [1].
Пятница вечер делать нечего
Не многим изестно, что директива proxy_pass [2] поддерживает не только локальные домены и потоки (aka upstream), но и внешние источники, например:
proxy_pass https://api.github.com/$request_uri
Так зародилась идея написать универсальный (с некоторыми оговорками) конфиг для nginx, который поддерживает любой переданный домен.
Мы можем объявлять новые переменные на основе глобальных c поддержой регулярных выражений с помощью map [3]:
map $request_url $my_request_path {
~*/(.*)$ $1;
default "";
}
Так, при запросе к http://example.com/api в переменной $my_request_path будет лежать api.
Мы можем отправлять клиенту дополнительные заголовки с помощью add_header [4]:
add_header X-Request-Path $my_request_path always;
Теперь у нас добавился заголовок X-Request-Path с значением api.
С помощью директивы proxy_set_header [5] добавлять заголовки к запросу, который отправляется proxy_pass. А с помощью proxy_hide_header [6] скрывать заголвки, которые мы получили от proxy_pass.
С помощью директивы if [7] обрабатывать выражения, например, при запросе методом OPTIONS отдавать сразу нужный код ответа:
if ($request_method = OPTIONS) {
return 204;
}
Для начала объявим $proxy_uri который мы будем извлекать из $request_uri:
map $request_uri $proxy_uri {
~*/http://(.*)/(.+)$ "http://$1/$2";
~*/https://(.*)/(.+)$ "https://$1/$2";
~*/http://(.*)$ "http://$1/";
~*/https://(.*)$ "https://$1/";
~*/(.*)/(.+)$ "https://$1/$2";
~*/(.*)$ "https://$1/";
default "";
}
Если коротко это работает так: при запросе http://example.com/example.ru, в переменной $proxy_uri будет лежать https://example.ru
Из полученного $proxy_uri извлечем часть, которая будет соответствовать заголовку Origin [8]:
map $proxy_uri $proxy_origin {
~*(.*)/.*$ $1;
default "";
}
Для заголовка Forwarded [9] нам понадобится обработать сразу 2 переменные:
map $remote_addr $proxy_forwarded_addr {
~^[0-9.]+$ "for=$remote_addr";
~^[0-9A-Fa-f:.]+$ "for="[$remote_addr]"";
default "for=unknown";
}
map $http_forwarded $proxy_add_forwarded {
"" "$proxy_forwarded_addr";
default "$http_forwarded, $proxy_forwarded_addr";
}
Обработка заголовока X-Forwarded-For [10] уже встроена в nginx [11]
Теперь мы можем перейти к объявлению нашего проксирующего сервера:
server {
listen 443 ssl;
server_name cors.example.com;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_request_body on;
proxy_redirect off;
resolver 77.88.8.8 77.88.8.1 8.8.8.8 8.8.4.4 valid=1d;
location / {
if ($proxy_uri = "") {
# empty uri
return 403;
}
# add proxy cors headers
add_header Access-Control-Allow-Headers "*" always;
add_header Access-Control-Allow-Methods "*" always;
add_header Access-Control-Allow-Origin "*" always;
if ($request_method = OPTIONS) {
return 204;
}
proxy_set_header Host $proxy_host;
proxy_set_header Origin $proxy_origin;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Forwarded "$proxy_add_forwarded;proto=$scheme";
proxy_pass $proxy_uri;
}
}
Мы получили минимально рабочий проксирующий сервер, у которого обрабатывается CORS Preflight Request [12] и добавляются соответствующие заголовки.
Все бы хорошо, но если у сервера, к которому мы проксируем, будет настроена обработка CORS, то его заголовки будут передаваться клиенту. Давайте скроем все возможные:
# hide original cors
proxy_hide_header Access-Control-Allow-Credentials;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Expose-Headers;
proxy_hide_header Access-Control-Max-Age;
proxy_hide_header Access-Control-Request-Headers;
proxy_hide_header Access-Control-Request-Method;
Хорошо бы еще передавать IP клиента, чтобы хоть как-то обходить rate limit, который может возникнуть, если несколько пользователей будут обращаться к одному ресурсу:
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Client-IP $remote_addr;
proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header Fastly-Client-IP $remote_addr;
proxy_set_header True-Client-IP $remote_addr;
proxy_set_header X-Cluster-Client-IP $remote_addr;
Мы же не говорим про анонимность, верно?)
И, напоследок, немного улучшим производительность выключив кэш/буферизацию/etc:
sendfile on;
tcp_nodelay on;
tcp_nopush on;
etag off;
if_modified_since off;
proxy_buffering off;
proxy_cache off;
proxy_cache_convert_head off;
proxy_max_temp_file_size 0;
client_max_body_size 0;
proxy_read_timeout 1m;
proxy_connect_timeout 1m;
reset_timedout_connection on;
gzip off;
gzip_proxied off;
# brotli off;
map $request_uri $proxy_uri {
~*/http://(.*)/(.+)$ "http://$1/$2";
~*/https://(.*)/(.+)$ "https://$1/$2";
~*/http://(.*)$ "http://$1/";
~*/https://(.*)$ "https://$1/";
~*/(.*)/(.+)$ "https://$1/$2";
~*/(.*)$ "https://$1/";
default "";
}
map $proxy_uri $proxy_origin {
~*(.*)/.*$ $1;
default "";
}
map $remote_addr $proxy_forwarded_addr {
~^[0-9.]+$ "for=$remote_addr";
~^[0-9A-Fa-f:.]+$ "for="[$remote_addr]"";
default "for=unknown";
}
map $http_forwarded $proxy_add_forwarded {
"" "$proxy_forwarded_addr";
default "$http_forwarded, $proxy_forwarded_addr";
}
server {
listen 443 ssl;
ssl_certificate /etc/letsencrypt/live/cors.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/cors.example.com/privkey.pem;
ssl_trusted_certificate /etc/letsencrypt/live/cors.example.com/chain.pem;
server_name cors.example.com;
sendfile on;
tcp_nodelay on;
tcp_nopush on;
etag off;
if_modified_since off;
proxy_buffering off;
proxy_cache off;
proxy_cache_convert_head off;
proxy_max_temp_file_size 0;
client_max_body_size 0;
proxy_http_version 1.1;
proxy_pass_request_headers on;
proxy_pass_request_body on;
proxy_read_timeout 1m;
proxy_connect_timeout 1m;
reset_timedout_connection on;
proxy_redirect off;
resolver 77.88.8.8 77.88.8.1 8.8.8.8 8.8.4.4 valid=1d;
gzip off;
gzip_proxied off;
# brotli off;
location / {
if ($proxy_uri = "") {
return 403;
}
# add proxy cors
add_header Access-Control-Allow-Headers "*" always;
add_header Access-Control-Allow-Methods "*" always;
add_header Access-Control-Allow-Origin "*" always;
if ($request_method = "OPTIONS") {
return 204;
}
# pass client to proxy
proxy_set_header Host $proxy_host;
proxy_set_header Origin $proxy_origin;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Client-IP $remote_addr;
proxy_set_header CF-Connecting-IP $remote_addr;
proxy_set_header Fastly-Client-IP $remote_addr;
proxy_set_header True-Client-IP $remote_addr;
proxy_set_header X-Cluster-Client-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header Forwarded "$proxy_add_forwarded;proto=$scheme";
# hide original cors
proxy_hide_header Access-Control-Allow-Credentials;
proxy_hide_header Access-Control-Allow-Headers;
proxy_hide_header Access-Control-Allow-Methods;
proxy_hide_header Access-Control-Allow-Origin;
proxy_hide_header Access-Control-Expose-Headers;
proxy_hide_header Access-Control-Max-Age;
proxy_hide_header Access-Control-Request-Headers;
proxy_hide_header Access-Control-Request-Method;
proxy_pass $proxy_uri;
}
}
Автор: Антон Петров
Источник [13]
Сайт-источник PVSM.RU: https://www.pvsm.ru
Путь до страницы источника: https://www.pvsm.ru/nginx/372092
Ссылки в тексте:
[1] cors-anywhere: https://github.com/Rob--W/cors-anywhere
[2] proxy_pass: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_pass
[3] map: https://nginx.org/en/docs/http/ngx_http_map_module.html#map
[4] add_header: https://nginx.org/en/docs/http/ngx_http_headers_module.html#add_header
[5] proxy_set_header: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_set_header
[6] proxy_hide_header: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#proxy_hide_header
[7] if: https://nginx.org/en/docs/http/ngx_http_rewrite_module.html#if
[8] Origin: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Origin
[9] Forwarded: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/Forwarded
[10] X-Forwarded-For: https://developer.mozilla.org/en-US/docs/Web/HTTP/Headers/X-Forwarded-For
[11] nginx: https://nginx.org/en/docs/http/ngx_http_proxy_module.html#variables
[12] CORS Preflight Request: https://developer.mozilla.org/en-US/docs/Glossary/Preflight_request
[13] Источник: https://habr.com/ru/post/651253/?utm_source=habrahabr&utm_medium=rss&utm_campaign=651253
Нажмите здесь для печати.