Aucun en-tête «Access-Control-Allow-Origin» n'est présent sur la ressource demandée - lorsque vous essayez d'obtenir des données à partir d'une API REST

470

J'essaie de récupérer certaines données de l'API REST de HP Alm. Cela fonctionne assez bien avec un petit script curl - j'obtiens mes données.

Maintenant, faire cela avec JavaScript, fetch et ES6 (plus ou moins) semble être un problème plus important. Je reçois toujours ce message d'erreur:

L'API de récupération ne peut pas se charger. La réponse à la demande de contrôle en amont ne passe pas la vérification du contrôle d'accès: aucun en-tête «Access-Control-Allow-Origin» n'est présent sur la ressource demandée. L'origine ' http://127.0.0.1:3000 ' n'est donc pas autorisée à y accéder. La réponse avait le code d'état HTTP 501. Si une réponse opaque répond à vos besoins, définissez le mode de la demande sur «no-cors» pour récupérer la ressource avec CORS désactivé.

Je comprends que cela est dû au fait que j'essaye de récupérer ces données depuis mon hôte local et que la solution devrait utiliser CORS. Maintenant, je pensais l'avoir fait, mais d'une manière ou d'une autre, il ignore ce que j'écris dans l'en-tête ou le problème est autre chose?

Alors, y a-t-il un problème de mise en œuvre? Suis-je en train de mal faire? Je ne peux malheureusement pas vérifier les journaux du serveur. Je suis vraiment un peu coincé ici.

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

J'utilise Chrome. J'ai également essayé d'utiliser ce plugin Chrome CORS, mais je reçois un autre message d'erreur:

La valeur de l'en-tête "Access-Control-Allow-Origin" dans la réponse ne doit pas être le caractère générique "*" lorsque le mode d'informations d'identification de la demande est "include". L'origine ' http://127.0.0.1:3000 ' n'est donc pas autorisée à y accéder. Le mode d'informations d'identification des demandes lancées par XMLHttpRequest est contrôlé par l'attribut withCredentials.

daniel.lozynski
la source

Réponses:

827

Cette réponse couvre beaucoup de terrain, elle est donc divisée en trois parties:

  • Comment utiliser un proxy CORS pour contourner les problèmes «Pas d'accès-Contrôle-Autoriser l'origine»
  • Comment éviter le contrôle en amont CORS
  • Comment fixer « header Access-Control-Allow-Origin ne doit pas être le caractère générique » problèmes

Comment utiliser un proxy CORS pour contourner les problèmes «Pas d'accès-Contrôle-Autoriser l'origine»

Si vous ne contrôlez pas le serveur auquel votre code JavaScript frontal envoie une demande et que le problème avec la réponse de ce serveur est simplement le manque de l'en- Access-Control-Allow-Origintête nécessaire , vous pouvez toujours faire fonctionner les choses en faisant la demande via un Proxy CORS. Pour montrer comment cela fonctionne, voici d'abord du code qui n'utilise pas de 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?"))

La raison pour laquelle le catchbloc est touché est que le navigateur empêche ce code d'accéder à la réponse qui revient https://example.com. Et la raison pour laquelle le navigateur le fait est que l'en- Access-Control-Allow-Origintête de réponse n'a pas d'en- tête de réponse.

Maintenant, voici exactement le même exemple mais juste avec un proxy CORS ajouté dans:

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?"))

Remarque: Si https://cors-anywhere.herokuapp.com est en panne ou indisponible lorsque vous l'essayez, voyez ci-dessous comment déployer votre propre serveur CORS Anywhere sur Heroku en seulement 2-3 minutes.

Le deuxième extrait de code ci-dessus peut accéder à la réponse avec succès, car le fait de prendre l'URL de la demande et de la modifier en https://cors-anywhere.herokuapp.com/https://example.com (en la préfixant simplement avec l'URL du proxy) provoque la demande de se faire via ce proxy, qui ensuite:

  1. Transmet la demande à https://example.com.
  2. Reçoit la réponse de https://example.com.
  3. Ajoute l'en- Access-Control-Allow-Origintête à la réponse.
  4. Transmet cette réponse, avec cet en-tête ajouté, au code frontal demandeur.

Le navigateur permet ensuite au code frontal d'accéder à la réponse, car cette réponse avec l'en- Access-Control-Allow-Origintête de réponse est ce que le navigateur voit.

Vous pouvez facilement exécuter votre propre proxy en utilisant le code de https://github.com/Rob--W/cors-anywhere/ .
Vous pouvez également déployer facilement votre propre proxy sur Heroku en seulement 2-3 minutes, avec 5 commandes:

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

Après avoir exécuté ces commandes, vous vous retrouverez avec votre propre serveur CORS Anywhere exécuté sur, par exemple, https://cryptic-headland-94862.herokuapp.com/ . Donc, plutôt que de préfixer l'URL de votre demande https://cors-anywhere.herokuapp.com, préfixez-la à la place avec l'URL de votre propre instance; par exemple, https://cryptic-headland-94862.herokuapp.com/https://example.com .

Donc, si vous essayez d'utiliser https://cors-anywhere.herokuapp.com, vous trouvez qu'il est en panne (ce qui sera parfois le cas), alors envisagez d'obtenir un compte Heroku (si ce n'est pas déjà fait) et prenez 2 ou 3 minutes pour effectuer les étapes ci-dessus pour déployer votre propre serveur CORS Anywhere sur Heroku.

Peu importe que vous exécutiez le vôtre ou que vous utilisiez https://cors-anywhere.herokuapp.com ou un autre proxy ouvert, cette solution fonctionne même si la demande est celle qui déclenche les navigateurs pour effectuer une OPTIONSdemande de contrôle en amont CORS, car dans ce cas, le le proxy renvoie également les en Access-Control-Allow-Headers- Access-Control-Allow-Methodstêtes et nécessaires au succès du contrôle en amont.


Comment éviter le contrôle en amont CORS

Le code de la question déclenche un contrôle en amont CORS, car il envoie un en- Authorizationtête.

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

Même sans cela, l'en- Content-Type: application/jsontête déclencherait également le contrôle en amont.

Qu'est-ce que le «contrôle en amont» signifie: avant que le navigateur n'essaye le POSTcode dans la question, il enverra d'abord une OPTIONSdemande au serveur - pour déterminer si le serveur accepte de recevoir une origine croisée POSTqui inclut les en Authorization- Content-Type: application/jsontêtes et .

Cela fonctionne assez bien avec un petit script curl - j'obtiens mes données.

Pour tester correctement avec curl, vous devez émuler la OPTIONSdemande de contrôle en amont que le navigateur envoie:

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"

… Avec https://the.sign_in.urlremplacé par votre sign_inURL réelle .

La réponse que le navigateur doit voir à partir de cette OPTIONSdemande doit inclure des en-têtes comme celui-ci:

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

Si la OPTIONSréponse n'inclut pas ces en-têtes, le navigateur s'arrêtera là et n'essaiera même jamais d'envoyer la POSTdemande. En outre, le code d'état HTTP pour la réponse doit être un 2xx, généralement 200 ou 204. S'il s'agit d'un autre code d'état, le navigateur s'arrête là.

Le serveur dans la question répond à la OPTIONSdemande avec un code d'état 501, ce qui signifie apparemment qu'il essaie d'indiquer qu'il n'implémente pas la prise en charge des OPTIONSdemandes. Les autres serveurs répondent généralement avec un code d'état 405 «Méthode non autorisée» dans ce cas.

Vous ne pourrez donc jamais faire de POSTdemandes directement à ce serveur à partir de votre code JavaScript frontal si le serveur répond à cette OPTIONSdemande avec un 405 ou 501 ou autre chose qu'un 200 ou 204 ou s'il ne répond pas avec ceux nécessaires en-têtes de réponse.

La façon d'éviter de déclencher un contrôle en amont pour le cas dans la question serait:

  • si le serveur ne nécessitait pas d'en- Authorizationtête de demande mais se fondait (par exemple) sur des données d'authentification intégrées dans le corps de la POSTdemande ou en tant que paramètre de requête
  • si le serveur n'a pas exigé que le POSTcorps ait un Content-Type: application/jsontype de média mais a plutôt accepté le POSTcorps comme application/x-www-form-urlencodedavec un paramètre nommé json(ou autre) dont la valeur est les données JSON

Comment fixer « header Access-Control-Allow-Origin ne doit pas être le caractère générique » problèmes

Je reçois un autre message d'erreur:

La valeur de l'en-tête "Access-Control-Allow-Origin" dans la réponse ne doit pas être le caractère générique "*" lorsque le mode d'informations d'identification de la demande est "include". L'origine ' http://127.0.0.1:3000 ' n'est donc pas autorisée à y accéder. Le mode d'informations d'identification des demandes lancées par XMLHttpRequest est contrôlé par l'attribut withCredentials.

Pour une demande qui inclut des informations d'identification, les navigateurs ne laisseront pas votre code JavaScript frontal accéder à la réponse si la valeur de l'en- Access-Control-Allow-Origintête de réponse est *. Au lieu de cela la valeur dans ce cas , doit correspondre exactement à l'origine de votre code frontend, http://127.0.0.1:3000.

Voir Demandes d'informations d'identification et caractères génériques dans l'article MDN HTTP access control (CORS).

Si vous contrôlez le serveur auquel vous envoyez la demande, une manière courante de traiter ce cas consiste à configurer le serveur pour qu'il prenne la valeur de l'en- Origintête de la demande et l'écho / la reflète dans la valeur de l'en- Access-Control-Allow-Origintête de la réponse. Par exemple, avec nginx:

add_header Access-Control-Allow-Origin $http_origin

Mais ce n'est qu'un exemple; d'autres systèmes de serveurs (Web) offrent des moyens similaires d'écho des valeurs d'origine.


J'utilise Chrome. J'ai également essayé d'utiliser ce plugin Chrome CORS

Ce plugin Chrome CORS injecte apparemment simplement un en- Access-Control-Allow-Origin: *tête dans la réponse que le navigateur voit. Si le plugin était plus intelligent, ce qu'il ferait est de définir la valeur de cette fausse Access-Control-Allow-Origintête de réponse à l'origine réelle de votre frontend code JavaScript, http://127.0.0.1:3000.

Évitez donc d'utiliser ce plugin, même pour les tests. C'est juste une distraction. Si vous voulez tester les réponses que vous obtenez du serveur sans navigateur les filtrant, il vaut mieux utiliser curl -Hcomme ci-dessus.


En ce qui concerne le code JavaScript frontal de la fetch(…)demande dans la question:

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

Supprimez ces lignes. Les en- Access-Control-Allow-*têtes sont des en- têtes de réponse . Vous ne voulez jamais leur envoyer de demande. Le seul effet que cela aura est de déclencher un navigateur pour effectuer un contrôle en amont.

sidehowbarker
la source
1
Ainsi, l'URL ressemblerait à: 127.0.0.1:9999/127.0.0.1:5000 avec des cors, non?
daniel.lozynski
Si vous exécutez votre propre instance du code github.com/Rob--W/cors-anywhere à 127.0.0.1:9999 et que vous souhaitez envoyer une demande à 127.0.0.1:5000 , oui
sideshowbarker
4
Superbe réponse, mon problème était que le serveur distant ne répondait pas aux demandes OPTIONS, donc après avoir tripoté les demandes et les en-têtes pour ce qui semblait être des âges, je l'ai résolu en supprimant les en-têtes Content-Typeet Access-Control-Allow-Origin- merci!
Morvael
1
Je n'aime pas l'idée d'utiliser un proxy à moins que ce soit celui que vous contrôlez, sinon vous récupérez les données via un tiers non fiable et il est ouvert à la falsification, etc.
DaveMongoose
1
C'est une réponse approfondie et superbe merci, ce que j'ai fait personnellement lors du test était Open Chrome avec indicateur de sécurité Web désactivé et cela a fonctionné. open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Karun
101

Cette erreur se produit lorsque l'URL du client et l'URL du serveur ne correspondent pas, y compris le numéro de port. Dans ce cas, vous devez activer votre service pour CORS qui est le partage de ressources d'origine croisée.

Si vous hébergez un service Spring REST, vous pouvez le trouver dans le billet de blog CORS support dans Spring Framework .

Si vous hébergez un service à l'aide d'un serveur Node.js,

  1. Arrêtez le serveur Node.js.
  2. npm install cors --save
  3. Ajoutez les lignes suivantes à votre server.js

    var cors = require('cors')
    
    app.use(cors()) // Use this after the variable declaration
    
Rakesh
la source
4
A travaillé sans aucun problème ... très utile avec le lien de ressource de printemps .. Merci beaucoup
Rajesh Mbm
4
including the port number:(
scottysseus
1
Vraiment utile, vous avez fait ma journée
kunal pal
le numéro de port m'a sauvé la vie
ashad
Réponse la plus simple jamais merci :)
cumule
47

Le problème est survenu parce que vous avez ajouté le code suivant comme en- tête de demande dans votre frontal:

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

Ces en-têtes appartiennent à la réponse , pas à la demande. Donc , retirez les, y compris la ligne:

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

Votre demande avait «Content-Type: application / json», ce qui a déclenché ce qu'on appelle le contrôle en amont CORS. Cela a causé le navigateur a envoyé la demande avec la méthode OPTIONS. Voir le contrôle en amont CORS pour des informations détaillées.
Par conséquent, dans votre back-end , vous devez gérer cette demande de contrôle en amont en renvoyant les en-têtes de réponse qui incluent:

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

Bien sûr, la syntaxe réelle dépend du langage de programmation que vous utilisez pour votre back-end.

Dans votre front-end, ça devrait être comme ça:

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
la source
4
Cela devrait être la meilleure réponse - je n'aime vraiment pas l'idée de contourner CORS, en particulier en le routant via un tiers.
DaveMongoose
Hé, c'est quoi "Header ()" s'il vous plait?
mitsu
@mitsu Si vous vous référez à la ligne: let headers = new Headers (); ci-dessus, il s'agit d'une interface de l'API fetch pour effectuer des actions avec les en-têtes de requête ou de réponse http. Visitez developer.mozilla.org/en-US/docs/Web/API/Headers pour obtenir les détails ainsi que des exemples sur son utilisation.
Lex Soft
1
Réponse claire et précise .. sa logique et répond exactement au problème. - Thx
Dhammika
7

Dans mon cas, j'utilise la solution ci-dessous

Front-end ou angulaire

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

back-end (j'utilise 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
la source
2

Juste mes deux cents ... concernant la façon d'utiliser un proxy CORS pour contourner les Access-Control-Allow-Originproblèmes «sans en- tête»

Pour ceux d'entre vous qui travaillent avec PHP sur le backend, déployer un "proxy CORS" est aussi simple que:

  1. créez un fichier nommé 'no-cors.php' avec le contenu suivant:

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

  2. sur votre front-end, faites quelque chose comme:

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

Yair Levy
la source
1

Enlève ça:

credentials: 'include',
Nalan Madheswaran
la source
1

L'utilisation a dataType: 'jsonp'fonctionné pour moi.

   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
la source
1

Dans mon cas, le serveur Web a empêché la méthode "OPTIONS"

Vérifiez votre serveur Web pour la méthode des options

J'utilise "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>

changer pour

<IfModule mod_rewrite.c>
  RewriteEngine off
  RewriteCond %{REQUEST_METHOD} ^OPTIONS
  RewriteRule .* . [F]
</IfModule>
Juneho Nam
la source
0

Je travaillais avec Spring REST et je l'ai résolu en ajoutant les AllowedMethods dans 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
la source
-1

J'obtenais cette erreur sur mon local. J'ai exécuté Visual Studio en tant qu'administrateur et cela a résolu.

ugurrrrr
la source