skip to content
usubeni fantasy logo Usubeni Fantasy

Explicar en detalle la estrategia de origen común

/ 11 min read

This Post is Available In: CN EN ES JA

El dolor de los novatos en frontend

Me gusta llamar a la política de mismo origen (Same-origin policy, a partir de ahora, SOP) como el dolor de los novatos en frontend.

Primero, hablemos de qué es el mismo origen: se considera el mismo origen cuando se cumple que tiene el mismo protocolo, host y puerto. Tomemos como ejemplo http://example.com:80, donde el protocolo es http, el host es example.com y el puerto es 80.

Acceder a recursos de diferentes orígenes conlleva algunas restricciones extrañas, así que vamos a enumerarlas a continuación.

Restricciones

Contaminación del canvas

Un canvas contaminado (solo escritura) no permite extraer imágenes del mismo, esto también ocurre con la carga de recursos en webGL:

Uncaught DOMException: Failed to execute 'getImageData' on 'CanvasRenderingContext2D': The canvas has been tainted by cross-origin data.

Obtención de información interna de iframes

Acceder a la mayoría de la información dentro de un iframe será denegado:

Uncaught DOMException: Blocked a frame with origin "http://localhost:5000" from accessing a cross-origin frame.

Fallo en las solicitudes Ajax

Por último, y lo más conocido por todos, las solicitudes Ajax pueden fallar:

Access to fetch at 'https://www.baidu.com/' from origin 'http://localhost:5000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource. If an opaque response serves your needs, set the request's mode to 'no-cors' to fetch the resource with CORS disabled.

Sin restricciones

A pesar de las restricciones impuestas por la política de mismo origen, hay algunos métodos que nos permiten usar recursos de diferentes orígenes:

  • Enlaces, redirecciones, envío de formularios
  • <script src="..."></script>
  • <link rel="stylesheet" href="...">
  • Etiquetas <img>, <video>, <audio>, <object>, <embed>
  • Fuentes aplicadas con @font-face
  • Carga de páginas web en iframes (puede ser bloqueada con X-Frame-Options)

¿Pero por qué? ¿Por qué una política de mismo origen tan estricta nos permite obtener datos utilizando los métodos mencionados anteriormente? En realidad, la respuesta está en su naturaleza fundamental:

Naturaleza

La verdad es que todos los escenarios sin restricciones solo permiten obtener datos, pero no modificarlos. Como se suele decir, se puede admirar desde lejos, pero no se puede tocar.

Todos los métodos mencionados anteriormente solo permiten obtener datos en modo de solo lectura; lo que obtienes no se puede modificar. Al abrir una página web en un iframe, puedes ver la página completa, pero no puedes interactuar con su ejecución.

El atributo action del formulario es un problema heredado. Aunque parece que enviar datos mediante POST es una operación de modificación, en realidad no se puede acceder a los datos de respuesta. Además, después de enviar el formulario, se redirecciona directamente a la dirección objetivo, esto es similar a los enlaces y las redirecciones. En su época, esto se usaba mucho en PHP.

En el caso de las solicitudes Ajax, en realidad, la naturaleza es que no se puede escribir en recursos de origen diferente (respuesta opaca). Tu solicitud es exitosa, pero el navegador te niega la capacidad de operar con los datos obtenidos. En otras palabras, la interceptación del resultado de la solicitud de un recurso en un origen diferente es un comportamiento del navegador.

Por qué existe

Sabiendo esto, es fácil deducir por qué existe SOP:

  • El problema de seguridad en la red se trata principalmente de prevenir el CSRF (falsificación de petición en sitios cruzados).
  • Evitar que el host pueda manipular de forma arbitraria el iframe.
  • Prohibir la edición arbitraria de archivos multimedia de imágenes y audio por parte del cliente.

Solución propuesta

El mismo origen de políticas (SOP, por sus siglas en inglés) proporciona una experiencia de usuario segura, pero no es muy amigable para el desarrollo. Los desarrolladores deben esforzarse adicionalmente para hacer frente a SOP.

Protocolo CORS

Para permitir el intercambio de respuestas en diferentes orígenes y permitir fetches más versátiles que lo posible con el elemento de formulario de HTML, existe el protocolo CORS. Está basado en HTTP y permite que las respuestas declaren que pueden ser compartidas con otros orígenes.

El protocolo CORS se utiliza para negociar si los recursos del servidor pueden ser leídos desde un origen diferente.

Correcto, CORS es un protocolo, específicamente un protocolo basado en HTTP. Se implementa mediante cabeceras de solicitud y respuesta HTTP, y el proceso es el siguiente:

  1. Cuando se encuentra una solicitud de origen cruzado y no es una solicitud simple (en cuyo caso se envía la solicitud real directamente).
  2. Se envía una solicitud de precarga (preflight).
  3. El servidor devuelve la información de configuración CORS.
  4. El navegador determina si permite el envío de la solicitud real.
  5. Se envía la solicitud real, y la respuesta de la solicitud real también incluirá la información de configuración CORS. Por lo tanto, el navegador permitirá que los desarrolladores utilicen los datos de respuesta (si es una solicitud simple, se enviará directamente; si falla la verificación de la información de configuración, se generará un error de origen cruzado).

La solicitud de precarga (preflight), a menudo se traduce como “solicitud de opciones”, es una solicitud HTTP de tipo OPTIONS. Las cabeceras clave de la solicitud son las siguientes:

  • Access-Control-Request-Method: el método de la solicitud real.
  • Access-Control-Request-Headers: los encabezados incluidos en la solicitud real.

El protocolo CORS utiliza cabeceras de respuesta HTTP para indicar las condiciones de permitir el origen cruzado. Por el nombre de estas cabeceras, podemos entender aproximadamente sus funciones en el protocolo CORS:

  • Access-Control-Allow-Methods: métodos permitidos.
  • Access-Control-Allow-Headers: encabezados permitidos.
  • Access-Control-Allow-Origin: origen permitido para el acceso.
  • Access-Control-Allow-Credentials: si se permite el acceso con credenciales.
  • Access-Control-Max-Age: tiempo de almacenamiento en caché para la información anterior.
  • Access-Control-Expose-Headers: los encabezados que JavaScript puede leer.

Entonces, ¿por qué se necesita una solicitud de precarga?

En mi opinión, como mencionamos anteriormente, el comportamiento del navegador es interceptar los resultados. La solicitud aún se envía correctamente al servidor y se ejecuta la lógica normal. Esto sería muy peligroso. Sin embargo, con la solicitud de precarga, se bloquea antes de que se realice la solicitud real y no se envía correctamente al servidor. Por otro lado, el protocolo CORS no bloquea las solicitudes simples para que lleguen al servidor. Esto se debe a que los métodos GET no modifican los datos y, por lo tanto, es difícil que tengan graves consecuencias. Además, puede haber razones históricas que justifiquen esto.

A continuación se muestra una implementación simple del servidor para el protocolo CORS:

fastify.addHook("preHandler", (req, res, done) => {
const allowedPaths = ["/cors-simple", "/cors"];
console.log(`\n${req.method}: ${req.url}\n`);
if (allowedPaths.includes(req.url)) {
res.header("Access-Control-Allow-Origin", "http://127.0.0.1:3000");
res.header("Access-Control-Allow-Methods", "GET,HEAD,PUT,PATCH,POST,DELETE");
res.header("Access-Control-Allow-Headers", "content-type,custom-header");
res.header("Access-Control-Allow-credentials", "true");
}
const isPreflight = /options/i.test(req.method);
if (isPreflight) {
return res.send();
}
done();
});

Una forma más madura de abordar este problema puede ser consultada en el fastify-cors.

Una vez que se ha negociado el protocolo CORS, el navegador permitirá que JavaScript acceda a datos de dominios remotos. Sin embargo, el problema no se resuelve tan fácilmente (por eso digo que la experiencia de desarrollo no es tan buena). Incluso si se puede leer datos de dominios remotos, seguirán existiendo problemas con las credenciales de las solicitudes, como las cookies:

El método fetch en sí no envía cookies de dominios cruzados de manera predeterminada, ni establece encabezados set-cookie en las respuestas. Para lograr esto, es necesario agregar la opción credentials: "include". Pero eso no es todo lo que se debe hacer, también se deben realizar las siguientes configuraciones:

  • Después de agregar el encabezado Access-Control-Allow-Credentials, las solicitudes dirigidas a dominios remotos podrán llevar credenciales.
  • Sin embargo, después de configurar el valor de Allow-Credentials, el valor de Access-Control-Allow-Origin no puede ser *, debe ser un origen único.
  • Solo se enviarán cookies con SameSite=None al servidor.
  • La opción Secure también es necesaria si SameSite=None está configurado.
  • Debe utilizarse el protocolo https, de lo contrario, no se podrá establecer una cookie Secure.

P.D. Hay una leve diferencia entre same site y same origin, pero en la mayoría de los casos no es necesario tenerlo en cuenta. Puede obtener más detalles en: The great SameSite confusion

A partir de Chrome 80 (Febrero de 2020), se ha introducido el encabezado SameSite en la respuesta HTTP: Set-Cookie: SameSite: Defaults to Lax. En este caso, los usuarios pueden experimentar problemas para iniciar sesión después de la actualización debido a que las cookies de dominio cruzado ya no se envían de manera predeterminada.

Por cierto, además del CORS, hay otro encabezado de solicitud similar llamado CORP. Este encabezado se utiliza para evitar que los recursos como <script> y <img> hagan referencia a dominios cruzados. El valor predeterminado es cross-origin.

Hoy en día, los navegadores actúan como si se estableciera Cross-Origin-Resource-Policy: cross-origin en cada respuesta que no tenga un encabezado CORP explícito.

Canvas limpio

El protocolo CORS también se aplica para solucionar el problema de leer imágenes desde la etiqueta canvas. Por defecto, las solicitudes de imágenes no son CORS y se debe agregar img.crossOrigin = 'anonymous' (cuando se agrega con JavaScript, ten en cuenta que se debe usar la notación de camello, escribirlo todo en minúscula no tendrá efecto, pero al agregarlo a la etiqueta HTML se escribe en minúscula) para cambiar el modo de solicitud.

La diferencia con antes es que antes se podía mostrar directamente imágenes de otro dominio en el canvas, pero al agregar crossOrigin, solo se pueden solicitar recursos que ya tienen encabezados de solicitud CORS. De lo contrario, la solicitud se bloqueará y la imagen no se mostrará:

Access to image at 'https://image.api.playstation.com/trophy/np/NPWR13281_00_00A03E8F7ED2727FADE2548E45F2781D32F5D048F6/B81B1B7DBEB337F763D736123661E1D0E8B59FEE.PNG' from origin 'http://localhost:5000' has been blocked by CORS policy: No 'Access-Control-Allow-Origin' header is present on the requested resource.

Las imágenes que se pueden solicitar correctamente a través de CORS se pueden agregar al canvas sin contaminarlo.

Proxy inverso

Además de agregar CORS, también puedes integrar servicios de interfaz de dominio cruzado y archivos de aplicaciones web en el mismo dominio a través de un proxy inverso.

En el entorno de desarrollo, puedes usar la configuración Webpack devServer.proxy o las opciones del servidor de Vite, como Server Options, con las que todos están familiarizados.

En cambio, en el entorno de producción, se suele utilizar el proxy inverso de Nginx.

postMessage

El método postMessage se utiliza principalmente para la comunicación de datos entre diferentes ventanas (por ejemplo, diferentes iframes):

Emisor

targetWindow.postMessage(message, targetOrigin, transfer);

Para enviar mensajes a otra ventana utilizando postMessage, primero debe obtener la variable window de la ventana de destino. Por ejemplo, en un iframe, puedes acceder a la propiedad contentWindow después de seleccionarlo con querySelector. Si usas el método window.open(), se devolverá directamente el objeto window de la ventana objetivo, y así sucesivamente.

El primer parámetro es el mensaje que se envía y se clonará profundamente. Además, podemos controlar el segundo parámetro targetOrigin para asegurarnos de que el origen de la ventana de destino sea el que especificamos.

Un ejemplo de uso práctico podría ser el siguiente:

main.html
iframe.contentWindow.postMessage({ jsondata: {}, 1: "hello" }, "http://localhost:3000");

Receptor

sub.html
window.addEventListener("message", (event) => {
if (event.origin === "http://127.0.0.1:3000") {
console.log("pass");
}
});

Este enfoque es viable porque en ambos lados de la comunicación se agrega un código de seguridad. Esto nos permite asegurarnos de que las partes involucradas en la comunicación sean confiables. El método más simple es utilizar event.origin para determinar si el origen del mensaje es confiable. De lo contrario, se corre el riesgo de sufrir ataques.

Websocket

El protocolo WebSocket permite evadir la política de misma fuente, pero pone una carga significativa en el servidor. Por lo tanto, nadie utilizaría Websocket en lugar de la interfaz de HTTP solo para evadir la política de misma fuente.

JSONP

Traduce el chino al español. Debes traducir cada línea.


Habilidades increíbles, lágrimas de la época, en pocas palabras, se trata de aprovechar una brecha en los archivos de JavaScript para poder hacer solicitudes de origen cruzado sin restricciones. Devolverá los datos envueltos en una función, y luego podrás leer los datos utilizando la función que has definido. En resumen, parece que ya nadie lo utiliza.

Resumen

  • Se considera el mismo origen si se utiliza el mismo protocolo, host y puerto.
  • La política de mismo origen (SOP) tiene como objetivo garantizar que solo se pueda leer la información que proviene del mismo origen.
  • La SOP proporciona un entorno de red más seguro para los usuarios.
  • En la actualidad, el principal método utilizado por los desarrolladores para superar las restricciones de origen cruzado es el protocolo CORS.
  • Los desarrolladores front-end aún necesitan abordar el problema de las cookies en el contexto de CORS.
  • Para la comunicación entre iframes de diferente origen, se puede utilizar el método postMessage.

Referencias

PD: Debido a las rápidas actualizaciones de los navegadores, es posible que las políticas de mismo origen mencionadas en el texto hayan cambiado. Por favor, tenlo en cuenta.


评论组件加载中……