El intercambio de recursos de origen cruzado (CORS) es un protocolo estándar que define la interacción entre un navegador y un servidor para manejar de forma segura las solicitudes HTTP de origen cruzado.

En pocas palabras, una solicitud HTTP de origen cruzado es una solicitud a un recurso específico, que se encuentra en un origen diferente, es decir, un dominio, protocolo y puerto, que el del cliente que realiza la solicitud.

Por razones obvias, los navegadores pueden solicitar varios recursos de origen cruzado, incluidas imágenes, CSS, archivos JavaScript, etc. Sin embargo, de forma predeterminada, el modelo de seguridad de un navegador denegará cualquier solicitud HTTP de origen cruzado realizada por scripts del lado del cliente.

Si bien este comportamiento es deseable, por ejemplo, para evitar diferentes tipos de ataques basados en Ajax, a veces necesitamos indicar al navegador que permita las solicitudes HTTP de origen cruzado de los clientes de JavaScript con CORS.

Para comprender mejor por qué CORS es útil en ciertos casos de uso, consideremos el siguiente ejemplo: un cliente JavaScript que se ejecuta en http: // localhost: 4200, y una API de servicio web RESTful de spring boot que escucha en https://ricardogeek.com/api .

En tal caso, el cliente debería poder consumir la API REST, que de forma predeterminada estaría prohibida. Para lograr esto, podemos habilitar fácilmente CORS para estos dos dominios específicos en el navegador simplemente anotando los métodos de la API del servicio web RESTful responsable de manejar las solicitudes de los clientes con la anotación @CrossOrigin.

En este artículo, aprenderemos cómo usar la anotación @CrossOrigin en la implementación de un servicio web RESTful en Spring Boot.

Dependencias del proyecto

Vamos a empezar a crear un servicio web RESTful básico. En este caso, la funcionalidad del servicio se limitará a solo obtener algunas entidades JPA de una base de datos H2 en memoria y devolverlas en formato JSON al cliente en el cuerpo de la respuesta.

Las dependencias de Maven del servicio son bastante estándar.

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>
 
    <groupId>com.javacodegeeks.crossorigin</groupId>
    <artifactId>com.javacodegeeks.crossorigin</artifactId>
    <version>0.1.0</version>
 
    <parent>
        <groupId>org.springframework.boot</groupId>
        <artifactId>spring-boot-starter-parent</artifactId>
        <version>2.1.3.RELEASE</version>
    </parent>
     
    <dependencies>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-web</artifactId>
        </dependency>
        <dependency>
            <groupId>org.springframework.boot</groupId>
            <artifactId>spring-boot-starter-data-jpa</artifactId>
        </dependency>
        <dependency>
            <groupId>com.h2database</groupId>
            <artifactId>h2</artifactId>
            <scope>runtime</scope>
        </dependency>
    </dependencies>
 
    <properties>
        <java.version>1.8</java.version>
    </properties>
 
    <build>
        <plugins>
            <plugin>
                <groupId>org.springframework.boot</groupId>
                <artifactId>spring-boot-maven-plugin</artifactId>
            </plugin>
        </plugins>
    </build>
 
</project>

Como se muestra arriba, incluimos el spring-boot-starter-web, ya que lo necesitaremos para crear el servicio RESTful, y el spring-boot-starter-data-jpa, para implementar la capa de persistencia con una sobrecarga mínima.

Finalmente, la base de datos H2 en memoria nos permitirá conservar nuestras entidades JPA, sin tener que realizar operaciones de base de datos costosas.

La Capa Del Dominio

Además, implementaremos una capa de dominio delgada, que incluirá una única clase de entidad JPA que llamaremos Usuario. Para simplificar, la entidad será solo un POJO cuya funcionalidad se limitará al modelo del usuario.

package com.ricardogeek.crossorigin;
 
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
 
@Entity
public class Usuario {
     
    @Id
    @GeneratedValue(strategy = GenerationType.AUTO)
    private long id;
    private String nombre;
     
    public Usuario(){}
     
    public Usuario(String nombre) {
        this.nombre = nombre;
    }
     
    public long getId() {
        return id;
    }
      
    public String getNombre() {
        return nombre;
    }
     
    @Override
    public String toString() {
        return "User{" + "id=" + id + ", name=" + name + '}';
    }
}

Como se puede ver, la implementación de la clase Usuario es bastante autoexplicativa. De hecho, el único detalle de implementación que vale la pena mencionar aquí es el uso de la anotación @Entity.

La anotación marca la clase como una entidad JPA, lo que significa que una implementación de JPA puede gestionarla. A menos que configuremos explícitamente una implementación diferente, Spring Boot usará Hibernate como la implementación JPA predeterminada.

Capa de Repositorio

Spring Boot hace que sea realmente fácil implementar capas de repositorio basadas en JPA, sin tener que continuar desde cero nuestra propia implementación DAO.

Por lo tanto, para tener una funcionalidad CRUD mínima en las instancias de la clase de Usuario que definimos anteriormente, solo necesitamos extender la interfaz CrudRepository de Spring Boot.

package com.ricardogeek.crossorigin;
 
import org.springframework.data.repository.CrudRepository;
import org.springframework.stereotype.Repository;
 
@Repository
public interface UsuarioRepository extends CrudRepository<User, Long>{}

Solo con definir una interfaz que amplíe la interfaz CrudRepository de Spring Boot es suficiente para tener una implementación completamente operativa en tiempo de ejecución, que proporciona una funcionalidad CRUD básica en las entidades de la JPA del usuario.

El Controlador REST

De hecho, la capa de repositorio es funcional de forma aislada. Pero, por supuesto, necesitamos implementar una capa de nivel superior sobre ella, lo que nos permite definir un punto final que pueden ser utilizados por diferentes clientes remotos para realizar solicitudes HTTP de origen cruzado al servicio REST.

Para lograr esto, necesitaremos crear un controlador REST anotado con la anotación @CrossOrigin. En pocas palabras, el controlador actuará como un nivel intermedio entre los clientes y la capa de repositorio.

Aquí está la implementación del controlador REST:

package com.ricardogeek.crossorigin;
 
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@CrossOrigin(origins = "http://localhost:8383")
public class UsuarioController {
 
    private final UsuarioRepository usuarioRepositorio;
 
    public UsuarioController(UsuarioRepository usuarioRepository) {
        this.usuarioRepository = usuarioRepository;
    }
     
    @GetMapping("/usuarios")
    public Iterable<Usuario> getUsuarios() {
        return usuarioRepository.findAll();
    }
}

Anotamos la clase UsuarioController con la anotación @RestController. Como resultado, Spring Boot automáticamente reunirá en JSON las entidades devueltas por el método getUsuarios (), que se anota con @GetMapping, y las enviará de vuelta al cliente en el cuerpo de la respuesta.

Con respecto a la implementación del método getUsers (), simplemente devuelve una implementación de Iterable para las entidades JPA persistentes en la base de datos H2. Esto hace que sea fácil iterar sobre las entidades usando una instrucción de bucle para cada uno.

Por supuesto, el detalle más relevante que vale la pena resaltar aquí es el uso de la anotación @CrossOrigin (origins = “http: // localhost: 8383”). Esto permite que el navegador maneje de forma segura las solicitudes HTTP de origen cruzado de un cliente cuyo origen es http: // localhost: 8383.

Especificamos este origen, ya que es el de nuestro cliente de ejemplo de JavaScript (más sobre esto más adelante). Por supuesto, siéntase libre de cambiarlo por otro diferente, para satisfacer sus necesidades personales.

Desde que colocamos la anotación @CrossOrigin a nivel de clase, habilita CORS en el navegador para todos los métodos de clase. En este caso, la clase implementa solo un método, pero podría, por supuesto, implementar varios métodos.

Usando @CrossOrigin para múltiples orígenes

En la implementación actual de la clase Usuario, la anotación @CrossOrigin solo permite solicitudes HTTP de origen cruzado desde un solo origen. Podemos adoptar un enfoque menos restrictivo y especificar múltiples orígenes, según las necesidades de cada caso de uso.

package com.ricardogeek.crossorigin;
 
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
@CrossOrigin(origins = {"http://ricardogeek.com:8383", "http://rgeek.pro:4200"})
public class UsuarioController {
 
    // ...
     
    @GetMapping("/usuarios")
    public Iterable<Usuario> getUsuarios() {
        return usuarioRepositories.findAll();
    }
}

Especificando @CrossOrigin a nivel del método

Además, podemos usar la anotación @CrossOrigin a nivel de método. Esto hace que sea fácil especificar más selectivamente a qué métodos podemos llamar a través de una solicitud HTTP de origen cruzado.

package com.ricardogeek.crossorigin;
 
import org.springframework.web.bind.annotation.CrossOrigin;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.RestController;
 
@RestController
public class UsuarioController {
 
    // ...
     
    @GetMapping("/usuarios")
    @CrossOrigin(origins = "http://ricardogeek.com:8383")
    public Iterable<Usuario> getUsuarios() {
        return usuarioRepository.findAll();
    }
}

Iniciar la aplicación Spring Boot

En este punto, deberíamos tener una idea bastante clara sobre cómo usar la anotación @CrossOrigin en la implementación de un controlador REST. Aun así, todavía tenemos que crear una clase típica de arranque de Spring Boot y ejecutar el servicio REST.

package com.ricardogeek.crossorigin;
 
import java.util.stream.Stream;
import org.springframework.boot.CommandLineRunner;
import org.springframework.boot.SpringApplication;
import org.springframework.boot.autoconfigure.SpringBootApplication;
import org.springframework.context.annotation.Bean;
 
@SpringBootApplication
public class Application {
     
    public static void main(String[] args) {
        SpringApplication.run(Application.class, args);
    }
     
    @Bean
    CommandLineRunner initialize(UsuarioRepository usuarioRepository) {
        return args -> {
            Stream.of("Susan", "Robert", "Laura").forEach(nombre -> {
                Usuario usuario = new Usuario(nombre);
                usuarioRepository.save(usuario);
            });
        };
    }
}

Como se muestra arriba, la clase Application implementa el método initialize(), que simplemente persiste en la base de datos de algunas entidades de usuario cuando se inicia la aplicación.

Para iniciar la aplicación podemos correr el siguiente comando en la terminal ubicándonos en el directorio del proyecto:

$mvn spring-boot:run

Una vez que iniciemos la aplicación, abramos el navegador y lo apuntamos a http://localhost:8080/usuarios

Al hacer clic en la pestaña JSON (si están en firefox), deberíamos ver la lista de entidades de usuario persistentes en la base de datos H2. Por lo tanto, esto significa que el servicio web RESTful funciona como se esperaba.

Conectándose desde el origen cruzado

Ahora basta con crear una pagina simple con HTML y javascript como esta:

<!doctype html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <title>Origen Cruzado</title>
        <meta charset="UTF-8">
        <meta name="viewport" content="width=device-width, initial-scale=1.0">
    </head>
    <body>
        <h1>Cliente JavaSvript</h1>
        <button id="btn">Obtener Usuarios</button>
        <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.3.1/jquery.min.js"></script>
        <script>
            $(document).ready(function () {
                const Url = 'http://localhost:8080/users';
                $("#btn").click(function () {
                    $.get(Url, function (data, status) {
                        console.log(data);
                    });
                });
            });
        </script>
    </body>
</html>

Y si lo intentamos ejecutar desde otro dominio, entonces el error del navegador CORS aparecerá inminentemente.

E instalarlo en los dominios que hemos marcado en nuestro controlador como permitidos para aceptar solicitudes.