В запрашиваемом ресурсе отсутствует заголовок «Access-Control-Allow-Origin» - при попытке получить данные из REST API

162

Я пытаюсь получить некоторые данные из REST API HP Alm. Он работает очень хорошо с небольшим завитком script - я получаю свои данные.

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

API-интерфейс Fetch не может загружаться. Ответ на предполетный запрос не проверка контроля доступа: нет заголовка "Access-Control-Allow-Origin" присутствовать на запрошенном ресурсе. Origin 'http://127.0.0.1:3000' является поэтому не допускается доступ. В ответе был код статуса 501. Если непрозрачный ответ отвечает вашим потребностям, установите режим запроса на "no-cors" для извлечения ресурса с отключенным CORS.

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

Итак, есть ли проблема с реализацией? Я делаю это неправильно? К сожалению, я не могу проверить журналы сервера. Я действительно немного застрял здесь.

function performSignIn() {

  let headers = new Headers();

  headers.append('Content-Type', 'application/json');
  headers.append('Accept', 'application/json');

  headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
  headers.append('Access-Control-Allow-Credentials', 'true');

  headers.append('GET', 'POST', 'OPTIONS');

  headers.append('Authorization', 'Basic ' + base64.encode(username + ":" + password));

  fetch(sign_in, {
      //mode: 'no-cors',
      credentials: 'include',
      method: 'POST',
      headers: headers
    })
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(error => console.log('Authorization failed : ' + error.message));
}

Я использую Chrome. Я также пробовал использовать этот плагин Chrome CORS, но потом я получаю еще одно сообщение об ошибке:

Значение заголовка "Access-Control-Allow-Origin" в ответе не должен быть подстановочным знаком '*', когда режим учетных данных запроса 'включают'. Origin 'http://127.0.0.1:3000' поэтому не допускается доступ. Режим учетных данных запросов, инициированных XMLHttpRequest контролируется атрибутом withCredentials.

Теги:
cors
fetch-api
preflight

6 ответов

288
Лучший ответ

Этот ответ охватывает много вопросов, поэтому он разделен на три части:

  • Как использовать прокси-сервер CORS для решения проблемы "Нет заголовка Access-Control-Allow-Origin"
  • Как избежать предполетной проверки CORS
  • Как исправить проблему "заголовок Access-Control-Allow-Origin не должен быть подстановочным знаком"

Как использовать прокси-сервер CORS для решения проблемы "Нет заголовка Access-Control-Allow-Origin"

Если вы не контролируете сервер, на который отправляет запрос ваш код JavaScript, и проблема с ответом с этого сервера заключается просто в отсутствии необходимого заголовка Access-Control-Allow-Origin, вы все равно можете заставить вещи работать: сделать запрос через прокси-сервер CORS. Чтобы показать, как это работает, сначала рассмотрим код, который не использует прокси-сервер CORS:

const url = "https://example.com"; // site that doesnt send Access-Control-*
fetch(url)
.then(response => response.text())
.then(contents => console.log(contents))
.catch(() => console.log("Cant access " + url + " response. Blocked by browser?"))

Причина, по которой блок catch срабатывает, заключается в том, что браузер предотвращает доступ кода к ответу, полученному с https://example.com. И причина, по которой браузер делает это, заключается в том, что в ответе отсутствует заголовок ответа Access-Control-Allow-Origin.

Теперь вот точно такой же пример, но только с добавленным прокси-сервером CORS:

const proxyurl = "https://cors-anywhere.herokuapp.com/";
const url = "https://example.com"; // site that doesnt send Access-Control-*
fetch(proxyurl + url) // https://cors-anywhere.herokuapp.com/https://example.com
.then(response => response.text())
.then(contents => console.log(contents))
.catch(() => console.log("Cant access " + url + " response. Blocked by browser?"))

Примечание. Если https://cors-anywhere.herokuapp.com не работает или недоступен во время его попытки, ознакомьтесь с инструкциями по развертыванию собственного сервера CORS Anywhere на Heroku ниже, всего за 2-3 минуты.

Второй фрагмент кода, приведенный выше, может успешно получить доступ к ответу, потому что взятие URL-адреса запроса и его изменение на https://cors-anywhere.herokuapp.com/https://example.com - путем простого добавления префикса к URL-адресу прокси-сервера - вызывает сделать запрос через тот прокси, который затем:

  1. Перенаправляет запрос на https://example.com.
  2. Получает ответ от https://example.com.
  3. Добавляет заголовок Access-Control-Allow-Origin к ответу.
  4. Передает этот ответ с добавленным заголовком обратно в запрашивающий код внешнего интерфейса.

Затем браузер позволяет коду внешнего интерфейса получить доступ к ответу, поскольку этот ответ с заголовком ответа Access-Control-Allow-Origin - это то, что видит браузер.

Вы можете легко запустить свой собственный прокси, используя код с https://github.com/Rob--W/cors-anywhere/.
Вы также можете легко развернуть свой собственный прокси в Heroku буквально за 2-3 минуты, используя 5 команд:

git clone https://github.com/Rob--W/cors-anywhere.git
cd cors-anywhere/
npm install
heroku create
git push heroku master

После выполнения этих команд вы получите свой собственный сервер CORS Anywhere, например https://cryptic -H eadland-94862.herokuapp.com/. Поэтому вместо префикса URL-адреса вашего запроса с помощью https://cors-anywhere.herokuapp.com вместо этого https://cors-anywhere.herokuapp.com префикс URL-адреса для своего собственного экземпляра; например, https://cryptic -H eadland-94862.herokuapp.com/https://example.com.

Так что, если вы попытаетесь использовать https://cors-anywhere.herokuapp.com, вы обнаружите, что он отключен (что иногда бывает), а затем рассмотрите возможность получения учетной записи Heroku (если вы этого еще не сделали) и возьмите 2 или 3 минут, чтобы выполнить описанные выше действия, чтобы развернуть свой собственный сервер CORS Anywhere на Heroku.

Независимо от того, управляете ли вы самостоятельно или использовать https://cors-anywhere.herokuapp.com или другие открытые прокси, это решение будет работать, даже если запрос один, который вызывает браузеры, чтобы сделать CORS предполетной OPTIONS запрос, потому что в этом случае прокси-сервер также отправляет обратно заголовки Access-Control-Allow-Headers и заголовки Access-Control-Allow-Methods необходимые для успешной предварительной проверки.


Как избежать предполетной проверки CORS

Код в вопросе запускает предварительную проверку CORS, поскольку отправляет заголовок Authorization.

https://developer.mozilla.org/en-US/docs/Web/HTTP/Access_control_CORS#Preflighted_requests

Даже без этого заголовок Content-Type: application/json также запускает предпечатную проверку.

Что означает "предварительная проверка": прежде чем браузер попытается выполнить POST в коде, о котором идет речь, он сначала отправит серверу запрос OPTIONS чтобы определить, подключается ли сервер к получению POST который включает в себя Authorization и Content-Type: application/json заголовки.

Это работает довольно хорошо с небольшим скриптом curl - я получаю свои данные.

Чтобы правильно выполнить тестирование с помощью curl, вам нужно эмулировать предварительный запрос OPTIONS посылает браузер:

curl -i -X OPTIONS -H "Origin: http://127.0.0.1:3000" \
    -H 'Access-Control-Request-Method: POST' \
    -H 'Access-Control-Request-Headers: Content-Type, Authorization' \
    "https://the.sign_in.url"

… С https://the.sign_in.url замененным тем, какой у вас фактический URL для sign_in.

Ответ, который браузер должен видеть из этого запроса OPTIONS должен включать заголовки, подобные этому:

Access-Control-Allow-Origin:  http://127.0.0.1:3000
Access-Control-Allow-Methods: POST
Access-Control-Allow-Headers: Content-Type, Authorization

Если ответ OPTIONS не включает эти заголовки, браузер сразу же остановится и даже не попытается отправить запрос POST. Кроме того, код состояния HTTP для ответа должен быть 2xx - обычно 200 или 204. Если это любой другой код состояния, браузер остановится прямо здесь.

Сервер в вопросе отвечает на запрос OPTIONS кодом состояния 501, что, очевидно, означает, что он пытается указать, что не реализует поддержку запросов OPTIONS. В этом случае другие серверы обычно отвечают кодом состояния 405 "Метод не разрешен".

Таким образом, вы никогда не сможете отправлять POST запросы напрямую на этот сервер из вашего внешнего кода JavaScript, если сервер отвечает на этот запрос OPTIONS с помощью 405 или 501 или чего-либо, кроме 200 или 204, или если не отвечает с этими необходимыми заголовками ответа,

Способ избежать запуска предполета для рассматриваемого вопроса:

  • если серверу не требовался заголовок запроса Authorization но вместо этого (например) использовались данные аутентификации, встроенные в тело запроса POST или в качестве параметра запроса
  • если серверу не требовалось, чтобы тело POST имело медиа-тип Content-Type: application/json но вместо этого приняло тело POST как application/x-www-form-urlencoded с параметром с именем json (или любым другим), значением которого является JSON данные

Как исправить проблему "заголовок Access-Control-Allow-Origin не должен быть подстановочным знаком"

Я получаю еще одно сообщение об ошибке:

Значение заголовка "Access-Control-Allow-Origin" в ответе не должно быть подстановочным знаком "*", если режим учетных данных запроса имеет значение "include". Поэтому источнику " http://127.0.0.1:3000 " запрещен доступ. Режим учетных данных запросов, инициируемых XMLHttpRequest, контролируется атрибутом withCredentials.

Для запроса, содержащего учетные данные, браузеры не разрешают вашему внешнему JavaScript-коду обращаться к ответу, если значение заголовка ответа Access-Control-Allow-Origin равно *. Вместо этого значение в этом случае должно точно соответствовать происхождению ваших кодов внешнего интерфейса, http://127.0.0.1:3000.

См. Учетные запросы и подстановочные знаки в статье MDN HTTP контроль доступа (CORS).

Если вы управляете сервером, на который вы отправляете запрос, то обычным способом решения этого случая является настройка сервера на получение значения заголовка запроса Origin и его отображение/отражение в значении Access-Control-Allow-Origin Заголовок ответа Access-Control-Allow-Origin. Например, с помощью nginx:

add_header Access-Control-Allow-Origin $http_origin

Но это только один пример; другие (веб) серверные системы предоставляют аналогичные способы отображения исходных значений.


Я использую Chrome. Я также пытался использовать этот плагин Chrome CORS

Этот плагин Chrome CORS, очевидно, просто вводит заголовок Access-Control-Allow-Origin: * в ответ, который видит браузер. Если бы плагин был умнее, то он установил бы значение этого поддельного заголовка ответа Access-Control-Allow-Origin к фактическому источнику вашего внешнего кода JavaScript, http://127.0.0.1:3000.

Поэтому избегайте использования этого плагина даже для тестирования. Это просто отвлечение. Если вы хотите проверить, какие ответы вы получаете с сервера, а браузер их не фильтрует, лучше использовать curl -H как указано выше.


Что касается внешнего кода JavaScript для запроса fetch(…) в вопросе:

headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
headers.append('Access-Control-Allow-Credentials', 'true');

Удалить эти строки. Заголовки Access-Control-Allow-* являются заголовками ответа. Вы никогда не хотите отправить их в запросе. Единственный эффект, который у вас есть, - это запуск браузера для предварительной проверки.

  • 0
    Таким образом, URL будет выглядеть так: 127.0.0.1:9999/127.0.0.1:5000 с cors, верно?
  • 0
    Если вы используете свой собственный экземпляр кода github.com/Rob--W/cors-anywhere по адресу 127.0.0.1:9999 и хотите сделать запрос на номер 127.0.0.1:5000 , да
Показать ещё 4 комментария
33

Эта ошибка возникает, когда URL-адрес клиента и сервера не совпадают, включая номер порта. В этом случае вам нужно включить службу для CORS, которая является совместным использованием ресурсов из разных источников.

Если у вас есть служба Spring REST, вы можете найти ее в блоге о поддержке CORS в Spring Framework.

Если вы размещаете сервис на сервере Node.js, то

  1. Остановите сервер Node.js.
  2. Npm установить Cors
  3. Добавьте следующие строки в ваш server.js

    var cors = require('cors')
    
    app.use(cors()) // Use this after the variable declaration
    
  • 0
    Большое спасибо :)
  • 2
    Работал без проблем ... очень помог с весенней ссылкой на ресурс .. Большое спасибо
Показать ещё 3 комментария
0

Проблема возникла из-за того, что вы добавили следующий код в качестве заголовка запроса в ваш интерфейс:

headers.append('Access-Control-Allow-Origin', 'http://localhost:3000');
headers.append('Access-Control-Allow-Credentials', 'true');

Эти заголовки принадлежат ответу, а не запросу. Поэтому удалите их, включая строку:

headers.append('GET', 'POST', 'OPTIONS');

Ваш запрос содержал "Content-Type: application/json", следовательно, вызвал то, что называется предварительным просмотром CORS. Это привело к тому, что браузер отправил запрос методом OPTIONS. Смотрите предварительную проверку CORS для получения подробной информации.
Поэтому в вашем бэкэнде вы должны обработать этот предварительно выданный запрос, возвращая заголовки ответа, которые включают:

Access-Control-Allow-Origin : http://localhost:3000
Access-Control-Allow-Credentials : true
Access-Control-Allow-Methods : GET, POST, OPTIONS
Access-Control-Allow-Headers : Origin, Content-Type, Accept

Конечно, фактический синтаксис зависит от языка программирования, который вы используете для своей серверной части.

В вашем интерфейсе это должно быть так:

function performSignIn() {
    let headers = new Headers();

    headers.append('Content-Type', 'application/json');
    headers.append('Accept', 'application/json');
    headers.append('Authorization', 'Basic ' + base64.encode(username + ":" +  password));
    headers.append('Origin','http://localhost:3000');

    fetch(sign_in, {
        mode: 'cors',
        credentials: 'include',
        method: 'POST',
        headers: headers
    })
    .then(response => response.json())
    .then(json => console.log(json))
    .catch(error => console.log('Authorization failed : ' + error.message));
}
0

Удали это:

credentials: 'include',
-2

Я работал с Spring REST и решил, добавив AllowedMethods в WebMvcConfigurer.

@Value( "${app.allow.origins}" )
    private String allowOrigins;    
@Bean
public WebMvcConfigurer corsConfigurer() {
            System.out.println("allow origin: "+allowOrigins);
            return new WebMvcConfigurerAdapter() {
                @Override
                public void addCorsMappings(CorsRegistry registry) {
                    registry.addMapping("/**")
                    //.allowedOrigins("http://localhost")
                    .allowedOrigins(allowOrigins)
                    .allowedMethods("PUT", "DELETE","GET", "POST");
                }
            };
        }
-2

Использование dataType: 'jsonp' работал для меня.

   async function get_ajax_data(){
       var _reprojected_lat_lng = await $.ajax({
                                type: 'GET',
                                dataType: 'jsonp',
                                data: {},
                                url: _reprojection_url,
                                error: function (jqXHR, textStatus, errorThrown) {
                                    console.log(jqXHR)
                                },
                                success: function (data) {
                                    console.log(data);

                                    // note: data is already json type, you
                                    //       just specify dataType: jsonp
                                    return data;
                                }
                            });


 } // function               

Ещё вопросы

Сообщество Overcoder
Наверх
Меню