Angular CORS с Nginx

Вопрос или проблема

В сети есть несколько вопросов на ту же тему, но ничего не сработало.

У меня есть серверXYZ, на котором работает приложение Angular, веб-приложение tomcat для аутентификации и сервер nginx. Приложение Angular на порту 4200, приложение tomcat на 8080. Все на одном хосте.

У приложения Angular есть файл environment.ts
(закомментированная строка — один из моих тестов, смотрите конфигурацию nginx ниже):

export const environment = {
  production: false,
//  apiUrl: 'http://serverXYZ:444/api'
  apiUrl: 'http://localhost:8080/backend'
};

Конфигурация nginx:

    server {
        listen 444;
    
            server_name serverXYZ;

            location / {
                    proxy_pass http://localhost:4200;
                    
                    //websocket
                    proxy_set_header HOST $host;
                    proxy_set_header X-Real-IP $remote_addr;
                    proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
                    proxy_set_header X-Forwarded-Proto $scheme;
                    proxy_pass_request_headers on;
                    proxy_http_version 1.0;
                    proxy_set_header Upgrade $http_upgrade;
                    proxy_set_header Connection "Upgrade";
                    proxy_read_timeout 120s;
            
                    if ($request_method = 'OPTIONS') {
                            add_header 'Access-Control-Allow-Origin' '*';
                            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                            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' '*';
                            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                    }
                    if ($request_method = 'GET') {
                            add_header 'Access-Control-Allow-Origin' '*';
                            add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                            add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                            add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                    }
    
            }

            location /api {
                proxy_pass http://localhost:8080/backend;

                if ($request_method = 'OPTIONS') {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                        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' '*';
                        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                }
                if ($request_method = 'GET') {
                        add_header 'Access-Control-Allow-Origin' '*';
                        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
                        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
                        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
                }

           }
}

У веб-приложения tomcat есть следующее в web.xml:

...
<!-- Встроенная реализация CORS в Tomcat -->
        <!--  https://tomcat.apache.org/tomcat-7.0-doc/config/filter.html -->
        <filter>
                <filter-name>CorsFilter</filter-name>
                <filter-class>org.apache.catalina.filters.CorsFilter</filter-class>
                <init-param>
                        <param-name>cors.allowed.headers</param-name>
                        <param-value>Content-Type,X-Requested-With,accept,Origin,Access-Control-Request-Method,Access-Control-Request-Headers,Authorization</param-value>
                </init-param>
                <init-param>
                    <param-name>cors.allowed.methods</param-name>
                    <param-value>GET,POST,HEAD,OPTIONS,PUT,DELETE</param-value>
                </init-param>
        </filter>
        <filter-mapping>
                <filter-name>CorsFilter</filter-name>
                <url-pattern>/*</url-pattern>
        </filter-mapping>
        <!-- Конец встроенной реализации CORS -->
...

Я использую свой компьютер, открываю браузер. С описанной конфигурацией, если я подключаюсь к http://serverXYZ:444, приложение отображается, но я получаю ошибку CORS при аутентификации (Ошибка CORS). Если вместо этого я использую конфигурацию с закомментированной строкой в environment.ts, аутентификация в браузере не говорит CORS, только 403. Конечно, API аутентификации протестирован и работает.

Я в недоумении, что не так? Также, решение этой проблемы с помощью nginx является моим предпочтительным вариантом, но не обязательно.

Правка:
Я добавляю еще одну информацию, я запускаю приложение angular с помощью “ng serve –disableHostCheck=true”. Не знаю, может быть, в этом случае следует использовать опцию “–publicHost” или “–host 0.0.0.0”?

Правка 2:
Еще одна вещь, которую я только что заметил, Firefox не выдает мне никаких ошибок для запроса OPTIONS, только Cors для POST.

Вы уже видели знаменитую статью “If is Evil”? В статье говорится, что

Единственные 100% безопасные вещи, которые можно сделать внутри if в контексте location, это:

return ...;
rewrite ... last;

В большинстве случаев эти разъяснения слишком строгие, обычно вы можете безопасно использовать любую директиву nginx из ngx_http_rewrite_module внутри блока if. Однако использование любой другой директивы, включая add_header, действительно небезопасно и может привести к непредсказуемым результатам. Вот как я бы написал такую конфигурацию:

map $request_method $route {
    GET        main;
    POST       main;
    OPTIONS    options;
    default    invalid;
}

map $request_method $api {
    GET        api;
    POST       api;
    OPTIONS    options;
    default    invalid;
}

server {
    listen 444;
    server_name serverXYZ;

    location / {
        try_files "" @$route;
    }

    location /api {
        try_files "" @$api;
    }

    location @main {
        # настройка подключения websocket
        proxy_set_header HOST $host;
        proxy_set_header X-Real-IP $remote_addr;
        proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
        proxy_set_header X-Forwarded-Proto $scheme;
        proxy_pass_request_headers on;
        proxy_http_version 1.0;
        proxy_set_header Upgrade $http_upgrade;
        proxy_set_header Connection "Upgrade";
        proxy_read_timeout 120s;
        # передача запроса
        proxy_pass http://localhost:4200;
        # добавление заголовков CORS к ответу
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    }

    location @api {
        # преобразование URI        
        rewrite ^/api(.*) /backend$1 break;
        # передача запроса
        proxy_pass http://localhost:8080;
        # добавление заголовков CORS к ответу
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length,Content-Range';
    }

    location @options {
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT,User-Agent,X-Requested-With,If-Modified-Since,Cache-Control,Content-Type,Range';
        add_header 'Access-Control-Max-Age' 1728000;
        add_header 'Content-Type' 'text/plain; charset=utf-8';
        add_header 'Content-Length' 0;
        return 204;
    }

    location @invalid {
        # метод не разрешен
        add_header Allow "GET, POST, OPTIONS";
        return 405;
    }
}

Ответ или решение

Настройка CORS в Angular с использованием Nginx

CORS (Cross-Origin Resource Sharing) – это механизм безопасности веб-браузеров, который позволяет или запрещает веб-страницам делать запросы к доменам, отличным от того, с которого они были загружены. Настройка CORS имеет решающее значение для корректной работы приложений, которые взаимодействуют с серверными API. В данном случае мы столкнулись с проблемами настройки CORS для Angular-приложения, работающего на Nginx в связке с сервером Tomcat.

Структура системы

У вас есть следующая архитектура:

  1. Angular-приложение запущенное на http://localhost:4200.
  2. Сервер Tomcat с вашим API на http://localhost:8080/backend.
  3. Сервер Nginx, обрабатывающий запросы и работающий на порту 444.

Проблема с CORS

Вы столкнулись с проблемами CORS, которые возникают из-за конфликтов между настройками Nginx и Tomcat. Основные ошибки:

  • Failed CORS request: возникает при отправке запросов к API из вашего Angular-приложения.
  • 403 Forbidden: означает, что запрос был отклонён на уровне сервера, скорее всего, из-за недостаточных прав при доступе к API.

Настройка Nginx для CORS

Рекомендуется использовать стандартные ключи управления CORS непосредственно в конфигурации Nginx. Ниже приводится структура более безопасной и упрощённой конфигурации Nginx без использования директив в условиях if, которые могут привести к непредсказуемому поведению.

map $request_method $cors {
    DEFAULT "";
    GET     "GET";
    POST    "POST";
    OPTIONS "OPTIONS";
}

server {
    listen 444;
    server_name serverXYZ;

    location / {
        proxy_pass http://localhost:4200;
        include proxy_params;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range';
        if ($cors = "OPTIONS") {
            add_header 'Content-Length' 0;
            return 204;
        }
    }

    location /api {
        proxy_pass http://localhost:8080/backend;
        include proxy_params;
        add_header 'Access-Control-Allow-Origin' '*';
        add_header 'Access-Control-Allow-Methods' 'GET, POST, OPTIONS';
        add_header 'Access-Control-Allow-Headers' 'DNT, User-Agent, X-Requested-With, If-Modified-Since, Cache-Control, Content-Type, Range';
        add_header 'Access-Control-Expose-Headers' 'Content-Length, Content-Range';
        if ($cors = "OPTIONS") {
            add_header 'Content-Length' 0;
            return 204;
        }
    }
}

Настройка Tomcat для обработки CORS

Убедитесь, что ваш сервер Tomcat также корректно обрабатывает запросы CORS. Ваша конфигурация в web.xml выглядит корректно, но убедитесь, что:

  1. Параметры корректны и включают все необходимые заголовки и методы.
  2. Фильтр CORS загружается для всех нужных URL-адресов.

Запуск Angular приложения

Обратите внимание на использование параметров при старте вашего Angular приложения:

  • Параметр --disableHostCheck=true полезен для разработки, но в продакшене лучше уточнить хост с помощью --host 0.0.0.0. Таким образом, ваше приложение будет доступно с других адресов, а не только с локального.

Отладка

  1. Откройте инструменты разработчика в вашем браузере и следите за вкладкой "Сеть" для анализа заголовков запросов.
  2. Проверьте консоль на наличие ошибок CORS и убедитесь, что определенные заголовки действительно отправляются.
  3. Тестируйте запросы с помощью инструментов вроде Postman, чтобы исключить проблемы на стороне клиента.

Заключение

Настройка CORS может оказаться сложной задачей, особенно в окружении с множеством компонентов. Правильная конфигурация сервера Nginx, а также серверного приложения Tomcat обеспечит вашему Angular-приложению необходимую безопасность и функциональность для успешного взаимодействия с API. Надеемся, что предоставленная информация будет полезной для решения ваших проблем с CORS.

Оцените материал
Добавить комментарий

Капча загружается...