Żądany zasób nie zawiera nagłówka „Access-Control-Allow-Origin” - podczas próby pobrania danych z interfejsu API REST

465

Próbuję pobrać niektóre dane z interfejsu API REST HP Alm. Działa całkiem nieźle z małym skryptem curl - otrzymuję moje dane.

Teraz robiąc to z JavaScript, pobieranie i ES6 (mniej więcej) wydaje się większym problemem. Ciągle pojawia się ten komunikat o błędzie:

Nie można załadować interfejsu API pobierania. Odpowiedź na żądanie inspekcji wstępnej nie przechodzi kontroli kontroli dostępu: Żądany zasób nie zawiera nagłówka „Kontrola dostępu - Zezwalaj na pochodzenie”. W związku z tym źródło „ http://127.0.0.1:3000 ” nie ma dostępu. Odpowiedź miała kod statusu HTTP 501. Jeśli nieprzezroczysta odpowiedź spełnia twoje potrzeby, ustaw tryb żądania na „no-cors”, aby pobrać zasób z wyłączonym CORS.

Rozumiem, że dzieje się tak, ponieważ próbuję pobrać te dane z mojego hosta lokalnego, a rozwiązaniem powinno być CORS. Teraz myślałem, że to zrobiłem, ale jakoś ignoruje to, co piszę w nagłówku, czy problem jest czymś innym?

Czy istnieje problem z implementacją? Czy robię to źle? Niestety nie mogę sprawdzić dzienników serwera. Naprawdę trochę tu utknąłem.

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));
}

Używam Chrome. Próbowałem też użyć tej wtyczki Chrome CORS, ale otrzymuję kolejny komunikat o błędzie:

Wartością nagłówka „Access-Control-Allow-Origin” w odpowiedzi nie może być symbol wieloznaczny „*”, gdy tryb poświadczeń żądania to „include”. W związku z tym źródło „ http://127.0.0.1:3000 ” nie ma dostępu. Tryb poświadczeń żądań inicjowanych przez XMLHttpRequest jest kontrolowany przez atrybut withCredentials.

daniel.lozynski
źródło

Odpowiedzi:

820

Ta odpowiedź obejmuje wiele gruntów, dlatego jest podzielona na trzy części:

  • Jak korzystać z serwera proxy CORS, aby obejść problemy „Brak nagłówka kontroli dostępu - zezwól na pochodzenie”
  • Jak uniknąć inspekcji wstępnej CORS
  • Jak naprawić problem „Nagłówek Access-Control-Allow-Origin nie może być znakiem wieloznacznym”

Jak korzystać z serwera proxy CORS, aby obejść problemy „Brak nagłówka kontroli dostępu - zezwól na pochodzenie”

Jeśli nie kontrolujesz serwera, twój kod JavaScript frontonu wysyła żądanie, a problemem z odpowiedzią z tego serwera jest po prostu brak niezbędnego Access-Control-Allow-Originnagłówka, możesz nadal działać - wysyłając żądanie za pomocą Serwer proxy CORS. Aby pokazać, jak to działa, najpierw oto kod, który nie używa proxy 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?"))

Powodem jest catchblokowanie przeglądarki, ponieważ przeglądarka uniemożliwia temu kodowi dostęp do odpowiedzi, z której pochodzi https://example.com. Powodem tego jest brak Access-Control-Allow-Originodpowiedzi w nagłówku odpowiedzi.

Oto dokładnie ten sam przykład, ale z dodanym serwerem proxy 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?"))

Uwaga: Jeśli https://cors-anywhere.herokuapp.com nie działa lub jest niedostępny podczas próby, zapoznaj się z poniższymi instrukcjami wdrażania własnego serwera CORS Anywhere w Heroku w zaledwie 2-3 minuty.

Drugi fragment kodu powyżej może uzyskać dostęp do odpowiedzi, ponieważ pobranie adresu URL żądania i zmiana go na https://cors-anywhere.herokuapp.com/https://example.com - po prostu poprzedzając go adresem URL proxy - powoduje żądanie wykonania za pośrednictwem tego serwera proxy, który następnie:

  1. Przekazuje żądanie do https://example.com.
  2. Otrzymuje odpowiedź od https://example.com.
  3. Dodaje Access-Control-Allow-Originnagłówek do odpowiedzi.
  4. Przekazuje tę odpowiedź z tym dodanym nagłówkiem z powrotem do żądającego kodu interfejsu użytkownika.

Przeglądarka zezwala następnie kodowi frontonu na dostęp do odpowiedzi, ponieważ ta odpowiedź z Access-Control-Allow-Originnagłówkiem odpowiedzi jest tym, co przeglądarka widzi.

Możesz łatwo uruchomić własny serwer proxy za pomocą kodu z https://github.com/Rob--W/cors-anywhere/ .
Możesz również łatwo wdrożyć własne proxy w Heroku w dosłownie zaledwie 2-3 minuty, za pomocą 5 poleceń:

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

Po uruchomieniu tych poleceń uzyskasz własny serwer CORS Anywhere działający np . Https://cryptic-headland-94862.herokuapp.com/ . Zamiast więc poprzedzać URL żądania https://cors-anywhere.herokuapp.com, należy go poprzedzić adresem URL własnego wystąpienia; np . https://cryptic-headland-94862.herokuapp.com/https://example.com .

Jeśli więc spróbujesz użyć https://cors-anywhere.herokuapp.com, okaże się, że nie działa (co czasami będzie), to rozważ założenie konta Heroku (jeśli jeszcze tego nie zrobiłeś) i weź 2 lub 3 minuty na wykonanie powyższych kroków, aby wdrożyć własny serwer CORS Anywhere na Heroku.

Niezależnie od tego, czy prowadzisz własny, czy używasz https://cors-anywhere.herokuapp.com lub innego otwartego serwera proxy, to rozwiązanie działa nawet wtedy, gdy jest to żądanie, które uruchamia przeglądarki w celu wykonania OPTIONSżądania inspekcji wstępnej CORS - ponieważ w takim przypadku proxy również odsyła nagłówki Access-Control-Allow-Headersi Access-Control-Allow-Methodsnagłówki potrzebne do pomyślnego przebiegu inspekcji wstępnej.


Jak uniknąć inspekcji wstępnej CORS

Kod w pytaniu uruchamia inspekcję wstępną CORS, ponieważ wysyła Authorizationnagłówek.

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

Nawet bez tego Content-Type: application/jsonnagłówek uruchomiłby również inspekcję wstępną.

Co oznacza „inspekcja wstępna”: zanim przeglądarka wypróbuje POSTkod w pytaniu, najpierw wyśle OPTIONSżądanie do serwera - w celu ustalenia, czy serwer zgadza się na otrzymanie źródła krzyżowego POSTzawierającego nagłówki Authorizationi Content-Type: application/json.

Działa całkiem nieźle z małym skryptem curl - otrzymuję moje dane.

Aby poprawnie przetestować curl, musisz emulować OPTIONSżądanie inspekcji wstępnej wysyłane przez przeglądarkę:

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"

… Z https://the.sign_in.urlzastąpionym przez rzeczywisty sign_inadres URL.

Odpowiedź, którą przeglądarka musi zobaczyć z tego OPTIONSżądania, musi zawierać takie nagłówki:

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

Jeśli OPTIONSodpowiedź nie zawiera tych nagłówków, przeglądarka zatrzyma się w tym miejscu i nigdy nawet nie spróbuje wysłać POSTżądania. Ponadto kod stanu HTTP dla odpowiedzi musi wynosić 2xx - zwykle 200 lub 204. Jeśli jest to inny kod stanu, przeglądarka zatrzyma się w tym miejscu.

Serwer w pytaniu odpowiada na OPTIONSżądanie kodem stanu 501, co najwyraźniej oznacza, że ​​próbuje wskazać, że nie implementuje obsługi OPTIONSżądań. Inne serwery zwykle odpowiadają kodem stanu 405 „Metoda niedozwolona” w tym przypadku.

Więc nigdy nie będziesz w stanie wysyłać POSTżądań bezpośrednio do tego serwera z kodu JavaScript frontonu, jeśli serwer odpowie na to OPTIONSżądanie 405 lub 501 lub czymkolwiek innym niż 200 lub 204 lub jeśli nie odpowie na te niezbędne nagłówki odpowiedzi.

Sposobem uniknięcia uruchomienia inspekcji wstępnej dla sprawy w pytaniu byłoby:

  • jeśli serwer nie wymagał Authorizationnagłówka żądania, ale zamiast tego (na przykład) polegał na danych uwierzytelniających osadzonych w treści POSTżądania lub jako parametr zapytania
  • jeśli serwer nie wymagał, aby POSTtreść miała Content-Type: application/jsontyp nośnika, ale zamiast tego zaakceptował POSTtreść jak application/x-www-form-urlencodedw przypadku parametru o nazwie json(lub cokolwiek innego), którego wartością są dane JSON

Jak naprawić problem „Nagłówek Access-Control-Allow-Origin nie może być znakiem wieloznacznym”

Otrzymuję kolejny komunikat o błędzie:

Wartością nagłówka „Access-Control-Allow-Origin” w odpowiedzi nie może być symbol wieloznaczny „*”, gdy tryb poświadczeń żądania to „include”. W związku z tym źródło „ http://127.0.0.1:3000 ” nie ma dostępu. Tryb poświadczeń żądań inicjowanych przez XMLHttpRequest jest kontrolowany przez atrybut withCredentials.

W przypadku żądania zawierającego poświadczenia przeglądarki nie pozwalają kodowi JavaScript frontonu uzyskać dostęp do odpowiedzi, jeśli wartość Access-Control-Allow-Originnagłówka odpowiedzi wynosi *. Zamiast wartość w tym przypadku należy dokładnie dopasować pochodzenie kodzie frontend, w http://127.0.0.1:3000.

Zobacz Poświadczone żądania i symbole wieloznaczne w artykule MDN kontroli dostępu HTTP (CORS).

Jeśli kontrolujesz serwer, na który wysyłasz żądanie, to typowym sposobem radzenia sobie z tą sprawą jest skonfigurowanie serwera tak, aby pobierał wartość Originnagłówka żądania i wysyłał echo / odbicie z powrotem do wartości Access-Control-Allow-Originnagłówka odpowiedzi. Na przykład z nginx:

add_header Access-Control-Allow-Origin $http_origin

Ale to tylko jeden przykład; inne (serwerowe) systemy serwerowe zapewniają podobne sposoby echa wartości początkowych.


Używam Chrome. Próbowałem też użyć tej wtyczki Chrome CORS

Ta wtyczka Chrome CORS najwyraźniej po prostu wstrzykuje Access-Control-Allow-Origin: *nagłówek do odpowiedzi, którą widzi przeglądarka. Jeśli wtyczka byli mądrzejsi, co to się robi jest ustawienie wartości tego fałszywy Access-Control-Allow-Originnagłówku odpowiedzi do rzeczywistego pochodzenia swojej frontend kod JavaScript http://127.0.0.1:3000.

Unikaj więc używania tej wtyczki, nawet do testowania. To tylko odwrócenie uwagi. Jeśli chcesz sprawdzić, jakie odpowiedzi otrzymujesz z serwera bez filtrowania ich przez przeglądarkę, lepiej użyć curl -Hpowyższej metody .


Jeśli chodzi o kod JavaScript frontonu dla fetch(…)żądania w pytaniu:

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

Usuń te linie. Te Access-Control-Allow-*nagłówki są odpowiedzi nagłówki. Nigdy nie chcesz wysyłać ich w żądaniu. Jedyny efekt, jaki będzie miał, to uruchomienie przeglądarki w celu przeprowadzenia inspekcji wstępnej.

sidehowbarker
źródło
1
Więc adres URL wyglądałby tak: 127.0.0.1:9999/127.0.0.1:5000 z cors, prawda?
daniel.lozynski
Jeśli prowadzisz własną instancję kodu github.com/Rob--W/cors-anywhere pod adresem 127.0.0.1:9999 i chcesz wysłać żądanie do 127.0.0.1:5000 , tak
sidehowbarker
4
Znakomita odpowiedź, mój problem polegał na tym, że zdalny serwer nie odpowiadał na żądania OPCJI, więc po tym, jak majstrowałem przy żądaniach i nagłówkach przez co wydawało się, że minęły wieki, rozwiązałem go, usuwając nagłówki Content-Typei Access-Control-Allow-Origin- dziękuję!
Morvael
1
Nie podoba mi się pomysł używania serwera proxy, chyba że jest to taki, który kontrolujesz, w przeciwnym razie pobierasz dane za pośrednictwem niezaufanego podmiotu trzeciego i jest on otwarty na manipulowanie itp.
DaveMongoose
1
To jest dogłębna i doskonała odpowiedź, dziękuję. Osobiście zrobiłem to, gdy testowałem Open Chrome z flagą bezpieczeństwa disable-web i działało. open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Karun
101

Ten błąd występuje, gdy adres URL klienta i adres URL serwera nie są zgodne, w tym numer portu. W takim przypadku musisz włączyć usługę dla CORS, która jest współużytkowaniem zasobów pochodzących z różnych źródeł.

Jeśli prowadzisz usługę Spring REST, możesz ją znaleźć w poście na blogu Obsługa CORS w Spring Framework .

Jeśli hostujesz usługę za pomocą serwera Node.js, to

  1. Zatrzymaj serwer Node.js.
  2. npm install cors --save
  3. Dodaj następujące wiersze do pliku server.js

    var cors = require('cors')
    
    app.use(cors()) // Use this after the variable declaration
Rakesh
źródło
4
Pracowałem bez żadnych problemów ... bardzo pomocny z linkiem do źródła wiosny ..
Wielkie
4
including the port number:(
scottysseus
1
Naprawdę pomocny uczyniłeś mój dzień
kunal kumpel
numer portu uratował mi życie
ashad
Najprostsza odpowiedź, jaką kiedykolwiek dziękuję :)
kumuluje się
46

Problem powstał, ponieważ w interfejsie dodałeś następujący kod jako nagłówek żądania :

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

Te nagłówki należą do odpowiedzi , a nie do żądania. Więc usunąć je, w tym wierszu:

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

Twoje żądanie zawierało „Content-Type: application / json”, dlatego uruchomiono tak zwaną inspekcję wstępną CORS. Spowodowało to, że przeglądarka wysłała żądanie metodą OPTIONS. Szczegółowe informacje można znaleźć w części Wstępna kontrola CORS .
Dlatego w swoim zapleczu musisz obsłużyć to wstępnie sprawdzone żądanie, zwracając nagłówki odpowiedzi, które obejmują:

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

Oczywiście faktyczna składnia zależy od języka programowania używanego do zaplecza.

W twoim interfejsie powinno to wyglądać tak:

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));
}
Lex Soft
źródło
4
To powinna być najlepsza odpowiedź - naprawdę nie podoba mi się pomysł ominięcia CORS, szczególnie poprzez przekierowanie go przez stronę trzecią.
DaveMongoose
hej, co to jest „Header ()”, proszę?
mitsu
@mitsu Jeśli odwołujesz się do wiersza: let headers = new Headers (); powyżej, jest to interfejs API pobierania do wykonywania działań z nagłówkami żądań HTTP lub odpowiedzi. Odwiedź developer.mozilla.org/en-US/docs/Web/API/Headers, aby uzyskać szczegółowe informacje, a także przykłady korzystania z niego.
Lex Soft
1
Jasna i precyzyjna odpowiedź ... jest logiczna i dokładnie rozwiązuje problem. - Thx
Dhammika
7

W moim przypadku korzystam z poniższego rozwiązania

Front-end lub Angular

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

back-end (używam 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);
Harrison O
źródło
2

Tylko moje dwa centy ... dotyczące korzystania z serwera proxy CORS w celu obejścia Access-Control-Allow-Originproblemów związanych z „ brakiem nagłówka”

Dla tych, którzy pracują z php na backendie, wdrożenie „proxy CORS” jest tak proste jak:

  1. utwórz plik o nazwie „no-cors.php” o następującej treści:

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

  2. z przodu wykonaj coś takiego:

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

Yair Levy
źródło
1

Usuń to:

credentials: 'include',
Nalan Madheswaran
źródło
1

Korzystanie dataType: 'jsonp'działało dla mnie.

   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
źródło
1

W moim przypadku serwer WWW zapobiegł metodzie „OPCJE”

Sprawdź swój serwer sieciowy pod kątem metody opcji

Używam „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>

zmień na

<IfModule mod_rewrite.c>
  RewriteEngine off
  RewriteCond %{REQUEST_METHOD} ^OPTIONS
  RewriteRule .* . [F]
</IfModule>
Juneho Nam
źródło
0

Pracowałem z Spring REST i rozwiązałem go, dodając Dozwolone metody do 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");
                }
            };
        }
Jorge Dominguez
źródło
-1

Ten błąd pojawiał się na moim komputerze lokalnym. Uruchomiłem Visual Studio jako administrator i to rozwiązało.

ugurrrrr
źródło