Breaking

Post Top Ad

Your Ad Spot

martes, 10 de diciembre de 2019

Fetch: Cross-Origin Requests

Si enviamos una fetchsolicitud a otro sitio web, probablemente fallará.
Por ejemplo, intentemos buscar http://example.com:
try {
  await fetch('http://example.com');
} catch(err) {
  alert(err); // Failed to fetch
}
Fetch falla, como se esperaba.
El concepto central aquí es el origen : un triplete de dominio / puerto / protocolo.
Las solicitudes de origen cruzado, aquellas enviadas a otro dominio (incluso un subdominio) o protocolo o puerto, requieren encabezados especiales desde el lado remoto.
Esa política se llama "CORS": Intercambio de recursos de origen cruzado.

¿Por qué se necesita CORS? Una breve historia

CORS existe para proteger Internet de los piratas informáticos malvados.
Seriamente. Hagamos una breve digresión histórica.
Durante muchos años, un script de un sitio no pudo acceder al contenido de otro sitio.
Esa regla simple pero poderosa fue la base de la seguridad de Internet. Por ejemplo, un script malvado del sitio web hacker.comno pudo acceder al buzón del usuario en el sitio web gmail.comLa gente se sintió segura.
JavaScript tampoco tenía ningún método especial para realizar solicitudes de red en ese momento. Era un lenguaje de juguete para decorar una página web.
Pero los desarrolladores web exigieron más poder. Se inventaron una variedad de trucos para evitar la limitación y realizar solicitudes a otros sitios web.

Usando formularios

Una forma de comunicarse con otro servidor era enviar un <form>allí. La gente lo envió <iframe>, solo para permanecer en la página actual, así:







<!-- form target -->
<iframe name="iframe"></iframe>

<!-- a form could be dynamically generated and submited by JavaScript -->
<form target="iframe" method="POST" action="http://another.com/…">
  ...
</form>
Por lo tanto, fue posible realizar una solicitud GET / POST a otro sitio, incluso sin métodos de red, ya que los formularios pueden enviar datos a cualquier lugar. Pero como está prohibido acceder al contenido de un <iframe>desde otro sitio, no fue posible leer la respuesta.
Para ser precisos, en realidad había trucos para eso, requerían guiones especiales tanto en el iframe como en la página. Por lo tanto, la comunicación con el iframe era técnicamente posible. En este momento no tiene sentido entrar en detalles, deje que estos dinosaurios descansen en paz.

Usando guiones

Otro truco era usar una scriptetiqueta. Un script podría tener cualquiera src, con cualquier dominio, como <script src="http://another.com/…">Es posible ejecutar un script desde cualquier sitio web.
Si un sitio web, por ejemplo, another.comtenía la intención de exponer datos para este tipo de acceso, se utilizaba el llamado protocolo "JSONP (JSON con relleno)".
Así es como funcionó.
Digamos que nosotros, en nuestro sitio, necesitamos obtener los datos http://another.com, como el clima:
  1. Primero, de antemano, declaramos una función global para aceptar los datos, por ejemplo gotWeather.
    // 1. Declare the function to process the weather data
    function gotWeather({ temperature, humidity }) {
      alert(`temperature: ${temperature}, humidity: ${humidity}`);
    }
  2. Luego hacemos una <script>etiqueta con src="http://another.com/weather.json?callback=gotWeather", usando el nombre de nuestra función como callbackparámetro de URL.
    let script = document.createElement('script');
    script.src = `http://another.com/weather.json?callback=gotWeather`;
    document.body.append(script);
  3. El servidor remoto another.comgenera dinámicamente un script que llama gotWeather(...)con los datos que quiere que recibamos.
    // The expected answer from the server looks like this:
    gotWeather({
      temperature: 25,
      humidity: 78
    });
  4. Cuando el script remoto se carga y ejecuta, se gotWeatherejecuta y, como es nuestra función, tenemos los datos.
Eso funciona y no viola la seguridad, porque ambas partes acordaron pasar los datos de esta manera. Y, cuando ambas partes están de acuerdo, definitivamente no es un truco. Todavía hay servicios que proporcionan dicho acceso, ya que funciona incluso para navegadores muy antiguos.
Después de un tiempo, aparecieron los métodos de red en el navegador JavaScript.
Al principio, las solicitudes de origen cruzado estaban prohibidas. Pero como resultado de largas discusiones, se permitieron solicitudes de origen cruzado, pero con nuevas capacidades que requieren una asignación explícita por parte del servidor, expresada en encabezados especiales.

Solicitudes simples

Hay dos tipos de solicitudes de origen cruzado:
  1. Peticiones simples
  2. Todos los otros.
Las solicitudes simples son, bueno, más simples de hacer, así que comencemos con ellas.
Una solicitud simple es una solicitud que cumple dos condiciones:
  1. Método simple : GET, POST o HEAD
  2. Encabezados simples : los únicos encabezados personalizados permitidos son:
    • Accept,
    • Accept-Language,
    • Content-Language,
    • Content-Typecon el valor application/x-www-form-urlencodedmultipart/form-datatext/plain.
Cualquier otra solicitud se considera "no simple". Por ejemplo, una solicitud con PUTmétodo o con un API-Keyencabezado HTTP no se ajusta a las limitaciones.
La diferencia esencial es que se puede hacer una "solicitud simple" con a <form>o a <script>, sin ningún método especial.
Por lo tanto, incluso un servidor muy antiguo debería estar listo para aceptar una solicitud simple.
Contrariamente a eso, las solicitudes con encabezados no estándar o, por ejemplo, método DELETEno se pueden crear de esta manera. Durante mucho tiempo, JavaScript no pudo hacer tales solicitudes. Por lo tanto, un servidor antiguo puede suponer que tales solicitudes provienen de una fuente privilegiada, "porque una página web no puede enviarlas".
Cuando intentamos hacer una solicitud no simple, el navegador envía una solicitud especial de "verificación previa" que le pregunta al servidor: ¿acepta o no tales solicitudes de origen cruzado?
Y, a menos que el servidor confirme explícitamente que con los encabezados, no se envía una solicitud no simple.
Ahora entraremos en detalles.

CORS para solicitudes simples

Si una solicitud es de origen cruzado, el navegador siempre le agrega Originencabezado.
Por ejemplo, si solicitamos https://anywhere.com/requesthttps://javascript.info/page, los encabezados serán como:



GET /request
Host: anywhere.com
Origin: https://javascript.info
...
Como puede ver, el Originencabezado contiene exactamente el origen (dominio / protocolo / puerto), sin una ruta.
El servidor puede inspeccionar Originy, si acepta aceptar dicha solicitud, agrega un encabezado especial Access-Control-Allow-Origina la respuesta. Ese encabezado debe contener el origen permitido (en nuestro caso https://javascript.info), o una estrella *Entonces la respuesta es exitosa, de lo contrario es un error.
El navegador desempeña el papel de un mediador de confianza aquí:
  1. Asegura que lo correcto Originse envíe con una solicitud de origen cruzado.
  2. Comprueba si los permisos Access-Control-Allow-Originestán permitidos en la respuesta; si existe, JavaScript puede acceder a la respuesta; de lo contrario, falla con un error.
Aquí hay un ejemplo de una respuesta permisiva del servidor:



200 OK
Content-Type:text/html; charset=UTF-8
Access-Control-Allow-Origin: https://javascript.info

Encabezados de respuesta

Para la solicitud de origen cruzado, por defecto JavaScript solo puede acceder a los denominados encabezados de respuesta "simples":
  • Cache-Control
  • Content-Language
  • Content-Type
  • Expires
  • Last-Modified
  • Pragma
Acceder a cualquier otro encabezado de respuesta provoca un error.
Tenga en cuenta:
¡No hay Content-Lengthencabezado en la lista!
Este encabezado contiene la longitud de respuesta completa. Entonces, si estamos descargando algo y nos gustaría rastrear el porcentaje de progreso, entonces se requiere un permiso adicional para acceder a ese encabezado (ver más abajo).
Para otorgar acceso de JavaScript a cualquier otro encabezado de respuesta, el servidor debe enviar el Access-Control-Expose-Headersencabezado. Contiene una lista separada por comas de nombres de encabezado no simples a los que debe hacerse accesible.
Por ejemplo:






200 OK
Content-Type:text/html; charset=UTF-8
Content-Length: 12345
API-Key: 2c9de507f2c54aa1
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Expose-Headers: Content-Length,API-Key
Con dicho Access-Control-Expose-Headersencabezado, el script puede leer Content-Lengthy los API-Keyencabezados de la respuesta.

Solicitudes "no simples"

Podemos usar cualquier método HTTP: no solo GET/POST, sino también PATCHDELETEy otros.
Hace algún tiempo, nadie podía imaginar que una página web pudiera hacer tales solicitudes. Por lo tanto, es posible que existan servicios web que traten un método no estándar como una señal: "Eso no es un navegador". Pueden tenerlo en cuenta al verificar los derechos de acceso.
Por lo tanto, para evitar malentendidos, cualquier solicitud "no simple", que no se pudo hacer en los viejos tiempos, el navegador no hace tales solicitudes de inmediato. Antes de enviar una solicitud preliminar, llamada "verificación previa", solicitando permiso.
Una solicitud de verificación previa utiliza un método OPTIONS, sin cuerpo y dos encabezados:
  • Access-Control-Request-Method El encabezado tiene el método de la solicitud no simple.
  • Access-Control-Request-Headers El encabezado proporciona una lista separada por comas de sus encabezados HTTP no simples.
Si el servidor acepta atender las solicitudes, debe responder con el cuerpo vacío, el estado 200 y los encabezados:
  • Access-Control-Allow-Methods debe tener el método permitido.
  • Access-Control-Allow-Headers debe tener una lista de encabezados permitidos.
  • Además, el encabezado Access-Control-Max-Agepuede especificar una cantidad de segundos para almacenar en caché los permisos. Por lo tanto, el navegador no tendrá que enviar una verificación previa para solicitudes posteriores que satisfagan los permisos dados.
Veamos cómo funciona paso a paso, por ejemplo, para una PATCHsolicitud de origen cruzado (este método se usa a menudo para actualizar datos):
let response = await fetch('https://site.com/service.json', {
  method: 'PATCH',
  headers: {
    'Content-Type': 'application/json',
    'API-Key': 'secret'
  }
});
Hay tres razones por las cuales la solicitud no es simple (una es suficiente):
  • Método PATCH
  • Content-Typeno es uno de: application/x-www-form-urlencodedmultipart/form-datatext/plain.
  • Encabezado "no simple" API-Key.

Paso 1 (solicitud de verificación previa)

Antes de enviar dicha solicitud, el navegador, por sí solo, envía una solicitud de verificación previa que se ve así:
OPTIONS /service.json
Host: site.com
Origin: https://javascript.info
Access-Control-Request-Method: PATCH
Access-Control-Request-Headers: Content-Type,API-Key
  • Método: OPTIONS.
  • El camino - exactamente la misma que la solicitud principal: /service.json.
  • Encabezados especiales de origen cruzado:
    • Origin - el origen de la fuente.
    • Access-Control-Request-Method - método solicitado
    • Access-Control-Request-Headers - una lista separada por comas de encabezados "no simples".

Paso 2 (respuesta previa al vuelo)

El servidor debe responder con el estado 200 y los encabezados:
  • Access-Control-Allow-Methods: PATCH
  • Access-Control-Allow-Headers: Content-Type,API-Key.
Eso permite la comunicación futura, de lo contrario se desencadena un error.
Si el servidor espera otros métodos y encabezados en el futuro, tiene sentido permitirlos por adelantado agregando a la lista:
200 OK
Access-Control-Allow-Methods: PUT,PATCH,DELETE
Access-Control-Allow-Headers: API-Key,Content-Type,If-Modified-Since,Cache-Control
Access-Control-Max-Age: 86400
Ahora el navegador puede ver que PATCHestá dentro Access-Control-Allow-MethodsContent-Type,API-Keyestá en la lista Access-Control-Allow-Headers, por lo que envía la solicitud principal.
Además, la respuesta de verificación previa se almacena en caché por tiempo, especificado por el Access-Control-Max-Ageencabezado (86400 segundos, un día), por lo que las solicitudes posteriores no causarán una verificación previa. Suponiendo que se ajustan a los permisos almacenados en caché, se enviarán directamente.

Paso 3 (solicitud real)

Cuando la verificación previa es exitosa, el navegador ahora realiza la solicitud principal. El algoritmo aquí es el mismo que para solicitudes simples.
La solicitud principal tiene Originencabezado (porque es de origen cruzado):
PATCH /service.json
Host: site.com
Content-Type: application/json
API-Key: secret
Origin: https://javascript.info

Paso 4 (respuesta real)

El servidor no debe olvidar agregar Access-Control-Allow-Origina la respuesta principal. Una verificación previa exitosa no se alivia de eso:
Access-Control-Allow-Origin: https://javascript.info
Entonces JavaScript puede leer la respuesta del servidor principal.
Tenga en cuenta:
La solicitud de verificación previa ocurre "detrás de escena", es invisible para JavaScript.
JavaScript solo obtiene la respuesta a la solicitud principal o un error si no hay permiso del servidor.

Cartas credenciales

Una solicitud de origen cruzado por defecto no trae ninguna credencial (cookies o autenticación HTTP).
Eso es poco común para las solicitudes HTTP. Por lo general, una solicitud http://site.comse acompaña de todas las cookies de ese dominio. Pero las solicitudes de origen cruzado hechas por métodos JavaScript son una excepción.
Por ejemplo, fetch('http://another.com')no envía ninguna cookie, incluso aquellas (!) Que pertenecen al another.comdominio.
¿Por qué?
Esto se debe a que una solicitud con credenciales es mucho más poderosa que sin ellas. Si está permitido, le otorga a JavaScript el poder total para actuar en nombre del usuario y acceder a información confidencial utilizando sus credenciales.
¿El servidor realmente confía tanto en el script? Luego, debe permitir explícitamente solicitudes con credenciales con un encabezado adicional.
Para enviar credenciales fetch, necesitamos agregar la opción credentials: "include", como esta:
fetch('http://another.com', {
  credentials: "include"
});
Ahora fetchenvía cookies que se originan another.comsin solicitud a ese sitio.
Si el servidor acepta aceptar la solicitud con credenciales , debe agregar un encabezado Access-Control-Allow-Credentials: truea la respuesta, además de Access-Control-Allow-Origin.
Por ejemplo:
200 OK
Access-Control-Allow-Origin: https://javascript.info
Access-Control-Allow-Credentials: true
Tenga en cuenta: Access-Control-Allow-Originestá prohibido usar una estrella *para solicitudes con credenciales. Como se muestra arriba, debe proporcionar el origen exacto allí. Esa es una medida de seguridad adicional, para garantizar que el servidor realmente sepa en quién confía para hacer tales solicitudes.

Resumen

Desde el punto de vista del navegador, hay dos tipos de solicitudes de origen cruzado: "simple" y todas las demás.
Las solicitudes simples deben cumplir las siguientes condiciones:
  • Método: GET, POST o HEAD.
  • Encabezados: solo podemos establecer:
    • Accept
    • Accept-Language
    • Content-Language
    • Content-Typeal valor application/x-www-form-urlencodedmultipart/form-datatext/plain.
La diferencia esencial es que las peticiones eran simples factible desde la antigüedad utilizando <form><script>etiquetas, mientras que los no-simples eran imposibles para los navegadores durante mucho tiempo.
Entonces, la diferencia práctica es que las solicitudes simples se envían de inmediato, con Originencabezado, mientras que para las demás el navegador realiza una solicitud preliminar de "verificación previa", pidiendo permiso.
Para solicitudes simples:
  • → El navegador envía Originencabezado con el origen.
  • ← Para solicitudes sin credenciales (no se envía por defecto), el servidor debe configurar:
    • Access-Control-Allow-Origin*o mismo valor queOrigin
  • ← Para solicitudes con credenciales, el servidor debe establecer:
    • Access-Control-Allow-Origin al mismo valor que Origin
    • Access-Control-Allow-Credentials a true
Además, para permitir el acceso JavaScript para cualquiera de las cabeceras de respuesta, excepto Cache-ControlContent-LanguageContent-TypeExpiresLast-ModifiedPragma, el servidor debe enumerar los permitidos en la Access-Control-Expose-Headerscabecera.
Para solicitudes no simples, se emite una solicitud preliminar de "verificación previa" antes de la solicitada:
  • → El navegador envía la OPTIONSsolicitud a la misma URL, con encabezados:
    • Access-Control-Request-Method ha solicitado el método
    • Access-Control-Request-Headers enumera encabezados solicitados no simples.
  • ← El servidor debe responder con el estado 200 y los encabezados:
    • Access-Control-Allow-Methods con una lista de métodos permitidos,
    • Access-Control-Allow-Headers con una lista de encabezados permitidos,
    • Access-Control-Max-Age con una cantidad de segundos para almacenar en caché los permisos.
  • Luego se envía la solicitud real, se aplica el esquema "simple" anterior.

Tareas

¿Por qué necesitamos Origin?

importancia: 5
Como probablemente sepa, hay un encabezado HTTP Referer, que generalmente contiene una URL de la página que inició una solicitud de red.
Por ejemplo, al ir a buscar http://google.comhttp://javascript.info/some/urllos encabezados de este aspecto:







Accept: */*
Accept-Charset: utf-8
Accept-Encoding: gzip,deflate,sdch
Connection: keep-alive
Host: google.com
Origin: http://javascript.info
Referer: http://javascript.info/some/url
Como puede ver, ambos RefererOriginestán presentes.
Las preguntas:
  1. ¿Por qué Origines necesario, si Referertiene aún más información?
  2. ¿Es posible que no haya RefererOrigin, o es incorrecto?

No hay comentarios.:

Publicar un comentario

Dejanos tu comentario para seguir mejorando!

Post Top Ad

Your Ad Spot

Páginas