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

470

Я пытаюсь получить некоторые данные из REST API 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.

daniel.lozynski
источник

Ответы:

828

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

  • Как использовать прокси-сервер 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 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 или другой открытый прокси-сервер, это решение работает, даже если это запрос, который запускает браузер для выполнения OPTIONSзапроса предварительной проверки CORS, потому что в этом случае прокси также отправляет обратно 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заменой на любой фактический sign_inURL.

Ответ, который браузер должен видеть на этот 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» в ответе не должно быть подстановочным знаком «*», если режим учетных данных запроса «включить». Поэтому источнику " http://127.0.0.1:3000 " запрещен доступ. Режим учетных данных запросов, инициируемых XMLHttpRequest, контролируется атрибутом withCredentials.

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

См. Учетные запросы и подстановочные знаки в статье MDN HTTP control access (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-*заголовки ответа заголовки. Вы никогда не хотите отправить их в запросе. Единственный эффект, который будет иметь место, - это запустить браузер для предварительной проверки.

sideshowbarker
источник
1
Таким образом, URL будет выглядеть так: 127.0.0.1:9999/127.0.0.1:5000 с cors, верно?
daniel.lozynski
Если вы используете свой собственный экземпляр github.com/Rob--W/cors-anywhere кода на 127.0.0.1:9999 и вы хотите сделать запрос на 127.0.0.1:5000 , да
sideshowbarker
4
Превосходный ответ, моя проблема заключалась в том, что удаленный сервер не отвечал на запросы OPTIONS, поэтому, поигравшись с запросами и заголовками в течение, казалось бы, целых веков, я решил их, удалив заголовки Content-Typeи Access-Control-Allow-Origin- спасибо!
Морваэль
1
Мне не нравится идея использовать прокси, если вы не контролируете его, в противном случае вы извлекаете данные через ненадежных третьих лиц, и они открыты для подделки и т. Д.
DaveMongoose
1
Это всесторонний и превосходный ответ, спасибо. Что я лично сделал, когда тестировал, был Open Chrome с флагом отключения веб-безопасности, и это сработало. open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Карун
101

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

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

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

  1. Остановите сервер Node.js.
  2. npm install cors --save
  3. Добавьте следующие строки в ваш server.js

    var cors = require('cors')
    
    app.use(cors()) // Use this after the variable declaration
Ракеш
источник
4
Работал без проблем ... очень помог с весенней ссылкой на ресурс .. Большое спасибо
Раджеш Мбм
4
including the port number:(
Скоттиссей
1
Действительно полезный ты сделал мой день
приятель
номер порта спас мне жизнь
ашад
Простейшее ответ когда - либо благодаря :)
складывает
47

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

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));
}
Лекс Софт
источник
4
Это должен быть главный ответ - мне действительно не нравится идея обойти CORS, особенно путем маршрутизации через сторонние организации.
DaveMongoose
эй, что за «Заголовок ()», пожалуйста?
Мицу
@mitsu Если вы ссылаетесь на строку: let headers = new Headers (); выше, то это интерфейс API выборки для выполнения действий с HTTP-запросом или заголовками ответа. Посетите developer.mozilla.org/en-US/docs/Web/API/Headers, чтобы получить подробную информацию, а также примеры его использования.
Lex Soft
1
Четкий и точный ответ .. его логично и точно решается вопрос. - Thx
Дхаммика
7

В моем случае я использую приведенное ниже решение

Передний или угловой

post(
    this.serverUrl, dataObjToPost,
    {
      headers: new HttpHeaders({
           'Content-Type':  'application/json',
         })
    }
)

back-end (я использую php)

header("Access-Control-Allow-Origin: http://localhost:4200");
header('Access-Control-Allow-Methods: GET, POST, OPTIONS');
header("Access-Control-Allow-Headers: Content-Type, Authorization");

$postdata = file_get_contents("php://input");
$request = json_decode($postdata);
print_r($request);
Харрисон О
источник
2

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

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

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

    $URL = $_GET['url']; echo json_encode(file_get_contents($URL)); die();

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

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

Яир Леви
источник
1

Удали это:

credentials: 'include',
Налан Мадхесваран
источник
1

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

В моем случае веб-сервер запретил метод «ОПЦИИ»

Проверьте ваш веб-сервер для метода параметров

Я использую "webtier"

/www/webtier/domains/[domainname]/config/fmwconfig/components/OHS/VCWeb1/httpd.conf

<IfModule mod_rewrite.c>
  RewriteEngine on
  RewriteCond %{REQUEST_METHOD} ^OPTIONS
  RewriteRule .* . [F]
</IfModule>

изменить на

<IfModule mod_rewrite.c>
  RewriteEngine off
  RewriteCond %{REQUEST_METHOD} ^OPTIONS
  RewriteRule .* . [F]
</IfModule>
Джунхо Нам
источник
0

Я работал с 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");
                }
            };
        }
Хорхе Домингес
источник
-1

Я получал эту ошибку на моем местном. Я запустил Visual Studio в качестве администратора, и это решило.

ugurrrrr
источник