No hay encabezado 'Access-Control-Allow-Origin' presente en el recurso solicitado, cuando se trata de obtener datos de una API REST

469

Estoy tratando de obtener algunos datos de la API REST de HP Alm. Funciona bastante bien con un pequeño script curl: obtengo mis datos.

Ahora, hacer eso con JavaScript, buscar y ES6 (más o menos) parece ser un problema mayor. Sigo recibiendo este mensaje de error:

Fetch API no se puede cargar. La respuesta a la solicitud de verificación previa no pasa la verificación de control de acceso: no hay encabezado 'Access-Control-Allow-Origin' en el recurso solicitado. Por lo tanto, el origen ' http://127.0.0.1:3000 ' no tiene acceso permitido. La respuesta tenía el código de estado HTTP 501. Si una respuesta opaca satisface sus necesidades, configure el modo de solicitud en 'no-cors' para recuperar el recurso con CORS deshabilitado.

Entiendo que esto se debe a que estoy tratando de obtener esos datos desde mi host local y la solución debería usar CORS. Ahora pensé que realmente hice eso, pero de alguna manera ¿ignora lo que escribo en el encabezado o el problema es otra cosa?

Entonces, ¿hay un problema de implementación? ¿Lo estoy haciendo mal? No puedo comprobar los registros del servidor por desgracia. Estoy realmente un poco atrapado aquí.

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

Estoy usando Chrome También intenté usar ese complemento Chrome CORS, pero luego recibo otro mensaje de error:

El valor del encabezado 'Access-Control-Allow-Origin' en la respuesta no debe ser el comodín '*' cuando el modo de credenciales de la solicitud es 'incluir'. Por lo tanto, el origen ' http://127.0.0.1:3000 ' no tiene acceso permitido. El modo de credenciales de las solicitudes iniciadas por XMLHttpRequest está controlado por el atributo withCredentials.

daniel.lozynski
fuente

Respuestas:

826

Esta respuesta cubre mucho terreno, por lo que se divide en tres partes:

  • Cómo usar un proxy CORS para solucionar los problemas de "Sin encabezado Access-Control-Allow-Origin"
  • Cómo evitar la verificación previa de CORS
  • Cómo corregir “encabezado Access-Control-Allow-Origen no debe ser el comodín” problemas

Cómo usar un proxy CORS para solucionar los problemas de "Sin encabezado Access-Control-Allow-Origin"

Si no controla el servidor, su código JavaScript frontend está enviando una solicitud, y el problema con la respuesta de ese servidor es simplemente la falta de la necesaria Access-Control-Allow-Origin encabezado , aún puede hacer que las cosas funcionen, haciendo la solicitud a través de un CORS proxy. Para mostrar cómo funciona, primero hay un código que no usa un 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 razón por la que el catchbloque se ve afectado es que el navegador evita que ese código acceda a la respuesta que regresa https://example.com. Y la razón por la que el navegador hace eso es que la respuesta carece del Access-Control-Allow-Originencabezado de respuesta.

Ahora, este es exactamente el mismo ejemplo, pero solo con un proxy CORS agregado en:

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

Nota: Si https://cors-anywhere.herokuapp.com está caído o no está disponible cuando lo prueba, vea a continuación cómo implementar su propio servidor CORS Anywhere en Heroku en solo 2-3 minutos.

El segundo fragmento de código anterior puede acceder a la respuesta con éxito porque toma la URL de solicitud y la cambia a https://cors-anywhere.herokuapp.com/https://example.com simplemente con el prefijo de la URL del proxy, provoca solicitar que se realice a través de ese proxy, que luego:

  1. Reenvía la solicitud a https://example.com .
  2. Recibe la respuesta de https://example.com .
  3. Agrega el Access-Control-Allow-Origin encabezado a la respuesta.
  4. Pasa esa respuesta, con ese encabezado agregado, de vuelta al código frontend solicitante.

Luego, el navegador permite que el código frontend acceda a la respuesta, porque esa respuesta con el Access-Control-Allow-Originencabezado de respuesta es lo que ve el navegador.

Puede ejecutar fácilmente su propio proxy utilizando el código de https://github.com/Rob--W/cors-anywhere/ .
También puede implementar fácilmente su propio proxy en Heroku en literalmente solo 2-3 minutos, con 5 comandos:

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

Después de ejecutar esos comandos, terminará con su propio servidor CORS Anywhere ejecutándose en, por ejemplo, https://cryptic-headland-94862.herokuapp.com/ . Entonces, en lugar de prefijar su URL de solicitud con https://cors-anywhere.herokuapp.com, prefije en su lugar con la URL de su propia instancia; por ejemplo, https://cryptic-headland-94862.herokuapp.com/https://example.com .

Entonces, si intenta utilizar https://cors-anywhere.herokuapp.com, encuentra que está inactivo (que a veces lo estará), considere obtener una cuenta de Heroku (si aún no lo hizo) y tome 2 o 3 minutos para realizar los pasos anteriores para implementar su propio servidor CORS Anywhere en Heroku.

Independientemente, ya sea que ejecute el suyo o use https://cors-anywhere.herokuapp.com u otro proxy abierto, esta solución funciona incluso si la solicitud es la que activa los navegadores para hacer una OPTIONSsolicitud de verificación previa CORS , porque en ese caso, el proxy también devuelve los encabezados Access-Control-Allow-Headersy Access-Control-Allow-Methodsnecesarios para que la verificación previa sea exitosa.


Cómo evitar la verificación previa de CORS

El código en la pregunta desencadena una verificación previa CORS, ya que envía un Authorization encabezado.

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

Incluso sin eso, el Content-Type: application/json encabezado también desencadenaría la verificación previa.

Qué significa "verificación previa": antes de que el navegador intente ingresar POSTel código en la pregunta, primero enviará una OPTIONSsolicitud al servidor, para determinar si el servidor está optando por recibir un origen cruzado POSTque incluye los encabezados Authorizationy Content-Type: application/json.

Funciona bastante bien con un pequeño script curl: obtengo mis datos.

Para realizar una prueba adecuada curl, debe emular la OPTIONSsolicitud de verificación previa que envía el navegador:

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"

... con https://the.sign_in.urlreemplazado por cual sea su sign_inURL real .

La respuesta que el navegador necesita ver de esa OPTIONSsolicitud debe incluir encabezados como este:

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

Si la OPTIONSrespuesta no incluye esos encabezados, entonces el navegador se detendrá allí y ni siquiera intentará enviar elPOST solicitud. Además, el código de estado HTTP para la respuesta debe ser 2xx, generalmente 200 o 204. Si se trata de cualquier otro código de estado, el navegador se detendrá allí.

El servidor en la pregunta está respondiendo a la OPTIONSsolicitud con un código de estado 501, lo que aparentemente significa que está tratando de indicar que no implementa soporte para OPTIONSsolicitudes. Otros servidores suelen responder con un código de estado 405 "Método no permitido" en este caso.

Por lo tanto, nunca podrá realizar POSTsolicitudes directamente a ese servidor desde su código JavaScript frontend si el servidor responde a esoOPTIONS solicitud con un 405 o 501 o cualquier otra cosa que no sea 200 o 204 o si no responde con los necesarios encabezados de respuesta.

La forma de evitar desencadenar una verificación previa para el caso en la pregunta sería:

  • si el servidor no requirió un Authorizationencabezado de solicitud sino que (por ejemplo) se basó en datos de autenticación incrustados en el cuerpo de la POSTsolicitud o como un parámetro de consulta
  • si el servidor no requirió que el POSTcuerpo tuviera un Content-Type: application/jsontipo de medio, sino que aceptó el POSTcuerpo como application/x-www-form-urlencodedcon un parámetro llamado json(o lo que sea) cuyo valor son los datos JSON

Cómo corregir “encabezado Access-Control-Allow-Origen no debe ser el comodín” problemas

Recibo otro mensaje de error:

El valor del encabezado 'Access-Control-Allow-Origin' en la respuesta no debe ser el comodín '*' cuando el modo de credenciales de la solicitud es 'incluir'. Por lo tanto, el origen ' http://127.0.0.1:3000 ' no tiene acceso permitido. El modo de credenciales de las solicitudes iniciadas por XMLHttpRequest está controlado por el atributo withCredentials.

Para una solicitud que incluya credenciales, los navegadores no permitirán que el código JavaScript de su interfaz acceda a la respuesta si el valor del Access-Control-Allow-Originencabezado de respuesta es *. En lugar del valor en ese caso debe coincidir exactamente con el origen de su código de interfaz, http://127.0.0.1:3000.

Ver solicitudes con credenciales y comodines en el artículo de control de acceso HTTP (CORS) de MDN.

Si controla el servidor al que está enviando la solicitud, una forma común de tratar este caso es configurar el servidor para que tome el valor del Originencabezado de la solicitud y haga eco / refleje de nuevo en el valor del Access-Control-Allow-Originencabezado de respuesta. Por ejemplo, con nginx:

add_header Access-Control-Allow-Origin $http_origin

Pero ese es solo un ejemplo; otros sistemas de servidor (web) proporcionan formas similares de eco de valores de origen.


Estoy usando Chrome También intenté usar ese complemento Chrome CORS

Ese complemento Chrome CORS aparentemente simplemente inyecta un Access-Control-Allow-Origin: *encabezado en la respuesta que ve el navegador. Si el plugin eran más inteligentes, lo que estaría haciendo es establecer el valor de esa falsa Access-Control-Allow-Origincabecera de respuesta al origen real de su interfaz de código JavaScript, http://127.0.0.1:3000.

Por lo tanto, evite usar ese complemento, incluso para las pruebas. Es solo una distracción. Si desea probar qué respuestas obtiene del servidor sin que el navegador las filtre, es mejor usarlas curl -Hcomo se indica arriba.


En cuanto al código JavaScript frontend para la fetch(…)solicitud en la pregunta:

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

Elimina esas líneas. Los Access-Control-Allow-*encabezados son encabezados de respuesta . Nunca desea enviarlos en una solicitud. El único efecto que tendrá es activar un navegador para hacer una verificación previa.

sidehowbarker
fuente
1
Entonces, la URL se vería así: 127.0.0.1:9999/127.0.0.1:5000 con cors, ¿verdad?
daniel.lozynski
Si está ejecutando su propia instancia del código github.com/Rob--W/cors-anywhere en 127.0.0.1:9999 y desea realizar una solicitud a 127.0.0.1:5000 , sí
sideshowbarker
44
Excelente respuesta, mi problema fue que el servidor remoto no respondía a las solicitudes de OPTIONS, así que después de jugar con las solicitudes y los encabezados durante lo que parecieron años, lo resolví eliminando los encabezados Content-Typey Access-Control-Allow-Origin, ¡gracias!
Morvael
1
No me gusta la idea de usar un proxy a menos que sea uno que usted controle, de lo contrario está obteniendo los datos a través de un tercero que no es de confianza y está abierto a manipulación, etc.
DaveMongoose
1
Esa es una respuesta profunda y excelente, gracias. Lo que hice personalmente cuando probé fue abrir Chrome con el indicador de seguridad de deshabilitar la web y funcionó. open /Applications/Google\ Chrome.app --args --disable-web-security --user-data-dir
Karun
101

Este error ocurre cuando la URL del cliente y la URL del servidor no coinciden, incluido el número de puerto. En este caso, debe habilitar su servicio para CORS, que es el intercambio de recursos de origen cruzado.

Si está alojando un servicio Spring REST, puede encontrarlo en la publicación de blog de soporte CORS en Spring Framework .

Si aloja un servicio utilizando un servidor Node.js, entonces

  1. Detenga el servidor Node.js.
  2. npm install cors --save
  3. Agregue las siguientes líneas a su server.js

    var cors = require('cors')
    
    app.use(cors()) // Use this after the variable declaration
Rakesh
fuente
44
Trabajó sin problemas ... muy útil con el enlace de recursos de primavera. Muchas gracias
Rajesh Mbm
44
including the port number:(
scottysseus
1
Muy útil u han hecho mi día
pal kunal
número de puerto me salvó la vida
ashad
La respuesta más simple jamás gracias :)
acumula
47

El problema surgió porque agregó el siguiente código como encabezado de solicitud en su front-end:

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

Esos encabezados pertenecen a la respuesta , no a la solicitud. Entonces quítelos , incluida la línea:

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

Su solicitud tenía 'Content-Type: application / json', por lo tanto, activó lo que se llama verificación previa CORS. Esto provocó que el navegador enviara la solicitud con el método OPTIONS. Consulte la verificación previa de CORS para obtener información detallada.
Por lo tanto, en su back-end , debe manejar esta solicitud con verificación previa devolviendo los encabezados de respuesta que incluyen:

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

Por supuesto, la sintaxis real depende del lenguaje de programación que utilice para su back-end.

En su front-end, debería ser así:

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
fuente
44
Esta debería ser la respuesta principal: realmente no me gusta la idea de omitir CORS, particularmente al enrutarlo a través de un tercero.
DaveMongoose
oye, ¿cuál es 'Header ()' por favor?
mitsu
@mitsu Si hace referencia a la línea: let headers = new Headers (); arriba, entonces es una interfaz de fetch API para realizar acciones con encabezados de solicitud o respuesta http. Visite developer.mozilla.org/en-US/docs/Web/API/Headers para obtener detalles y ejemplos sobre cómo usarlo.
Lex Soft
1
Respuesta clara y precisa ... es lógico y aborda exactamente el problema. - Thx
Dhammika
7

En mi caso, uso la siguiente solución

Frontal o angular

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

back-end (uso 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
fuente
2

Solo mis dos centavos ... con respecto a cómo usar un proxy CORS para Access-Control-Allow-Originsolucionar problemas de "Sin encabezado"

Para aquellos de ustedes que trabajan con php en el back-end, implementar un "proxy CORS" es tan simple como:

  1. cree un archivo llamado 'no-cors.php' con el siguiente contenido:

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

  2. en su front-end, haga algo como:

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

Yair Levy
fuente
1

Quita esto:

credentials: 'include',
Nalan Madheswaran
fuente
1

Usar dataType: 'jsonp'funcionó para mí.

   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
fuente
1

En mi caso, el servidor web impidió el método "OPCIONES"

Verifique su servidor web para el método de opciones

Estoy usando "webtier"

/www/webtier/domains/[domainnamefont>/config/fmwconfig/components/OHS/VCWeb1/httpd.conf

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

cambiar a

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

Estaba trabajando con Spring REST, y lo resolví agregando los métodos permitidos en 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
fuente
-1

Estaba recibiendo este error en mi local. Ejecuté Visual Studio como administrador y se resolvió.

ugurrrrr
fuente