it-roy-ru.com

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

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

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

Fetch API не может загрузить. Ответ на запрос предварительной проверки не Проходит проверку контроля доступа: заголовок «Access-Control-Allow-Origin» отсутствует На запрошенном ресурсе. Источник ' http://127.0.0.1:3000 ' является , Поэтому доступ запрещен. Ответ имеет код состояния HTTP 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» в ответе Не должно быть подстановочным знаком «*», если режим учетных данных запроса «Включает». Происхождение ' http://127.0.0.1:3000 ' поэтому не допускается Доступ. Режим учетных данных запросов, инициируемых XMLHttpRequest, управляется атрибутом withCredentials.

183
daniel.lozynski

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

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

Как использовать прокси-сервер CORS «Нет заголовка Access-Control-Allow-Origin»problem

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

const url = "https://example.com"; // site that doesn’t send Access-Control-*
fetch(url)
.then(response => response.text())
.then(contents => console.log(contents))
.catch(() => console.log("Can’t 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 doesn’t 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("Can’t 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-headland-94862.herokuapp.com/ . Поэтому вместо префикса URL запроса с https://cors-anywhere.herokuapp.com вместо этого используйте префикс URL для вашего собственного экземпляра; например, https://cryptic-headland-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 не должен быть подстановочным знаком»problem

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

Значение заголовка «Access-Control-Allow-Origin» в ответе Не должно быть подстановочным знаком «*», если режим учетных данных запроса «Включает». Происхождение ' 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 заголовок. Например, с помощью 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-* - это responseheaders. Вы никогда не захотите отправлять их в запросе. Единственный эффект, который у вас будет - это запустить браузер для предварительной проверки.

339
sideshowbarker

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

Если у вас есть служба 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
    
39
Rakesh

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

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 preflight для получения подробной информации. 
Поэтому в вашем back-end вы должны обработать этот предварительно выданный запрос, возвращая заголовки ответа, которые включают:

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));
}
1
Lex Soft

Использование 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               
0
hoogw

Удали это:

credentials: 'include',
0
Nalan Madheswaran

Только мои два цента ... относительно Как использовать прокси-сервер CORS, чтобы обойти проблемы «Нет заголовка Access-Control-Allow-Origin»

Для тех из вас, кто работает с php на сервере, развертывание «прокси-сервера CORS» так же просто, как:

  1. создайте файл с именем «no-cors.php» со следующим содержимым:

    $ URL = $ _GET ['url']; echo json_encode (file_get_contents ($ URL)); умереть();

  2. на вашем интерфейсе сделайте что-то вроде:

    fetch (' https://example.com/no-cors.php ' + '? url =' + url) .then (response => {/Handle Response/})

0
Yair Levy

Я работал с 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");
                }
            };
        }
0
Jorge Dominguez