Arquitectura REST


Introducción

El desarrollo de aplicaciones requiere de una gran diversidad de recursos que la mayoría de las veces ya están hechos y lo único que tienes que hacer es consumirlos y combinarlos para construir tu aplicación. Por ejemplo, cuando requieres desarrollar una aplicación de entregas, requieres del uso de mapas para poder seguir el envío; el desarrollar estos mapas e invertir en toda la infraestructura para implementarlo resultaría muy costoso. Es por lo que existe Google Maps, el cual te puede apoyar con la construcción de rutas a través de sus mapas para que puedas desarrollar tu aplicación, lo que representaría un menor costo. Al servicio que consumes para obtener los mapas se le llama API (Application Programming Interface) y existe una gran cantidad que puede ser consumida o tú puedes crear la tuya. Las aplicaciones web en la actualidad trabajan con este tipo de servicios porque permite separar la lógica de negocio, divide las tareas entre un servidor y un cliente. En este tema aprenderás los conceptos de una API, así como a desarrollar tu propia API REST en Spring, y también cómo puedes aplicarle métodos que la harán más segura a través de un sistema de autorización mediante el uso de tokens.


Explicación

Conceptos de servicios REST

De acuerdo con Gaitatzis (2019), las APIs (Interface Programming Interfaces) o Interfaces de Programación de Aplicaciones son programas informáticos que hablan con otros programas informáticos. Proporcionan una interfaz para que las aplicaciones programadas se comuniquen, y se están convirtiendo en un gran negocio.

REST (Representational State Transfer) o Transferencia de Estado Representacional forman parte de una clasificación de las APIs que deben estar alojadas en un servidor web y “estas herramientas de análisis de datos se construyen de forma que puedan ser utilizadas por productos de terceros, como aplicaciones móviles y otros sitios web (Gaitatzis, 2019).

El objetivo de API REST es enviar la información desde la base de datos procesada para que el cliente pueda consumirla más fácilmente. Permiten a las empresas crear nuevos flujos de ingresos a un costo casi nulo, mediante la monetización de sus servicios existentes. Las API REST te permiten comunicarte más fácilmente con el servidor. Por ejemplo, cuando visitas una tienda virtual y realizas la búsqueda de un producto haces una petición que es procesada mediante una API REST y te regresa la información del producto sin que tú tengas que buscar en una base de datos o una hoja de excel, lo que facilita la experiencia de usuario.

API REST ocupa diferentes tipos de archivos para representar los datos intercambiados entre el cliente y el servidor:

  • Texto plano
  • HTML
  • JSON
  •  XML

JSON es el formato más común porque es más fácil procesar debido a que es un lenguaje ligero y de lectura rápida. A través de estos archivos se intercambia información entre el servidor y el cliente.

{

 "nombre": "Juan",

 "apellido": "Perez",

 "edad": 18

}

Tabla 1. Ejemplo de JSON.

Estructura de rutas

La arquitectura REST requiere que cada función sea accesible a través de una única URL o endpoint. Los métodos HTTP “representan acciones, cada punto representa un tipo de recurso único, los datos de las estructuras representan el tipo de recurso y los datos estructurados representan el recurso” (Gaitatzis, 2019).

La arquitectura REST cuenta con diferentes métodos que representan las operaciones que el cliente desea realizar sobre los datos que solicita. Las operaciones básicas para manipular la información de una base de datos que son leer, crear, actualizar y borrar. Estas operaciones se representan mediante el acrónimo creado a partir del inglés CRUD (Create, Read, Update, Delete) y se realizan a través de una petición al servidor. Cada vez que el cliente requiere realizar alguna operación de tipo CRUD, se hace una petición al servidor llamada HTTP request.

Figura 1. Elementos de una HTTP request.

Las principales peticiones o HTTP request que se pueden realizar para un CRUD son GET, POST, PUT, PATCH DELETE.También son conocidos como verbos HTTP y Mozilla (2021) los define a continuación:

Figura 2. Principales peticiones HTTP.

Respuestas HTTP
Una vez que se realiza una petición al servidor, este tiene responder y lo hace a través de una respuesta identificada como HTTP response.

Figura 3. Elementos de una HTTP response.

Los HTTP response devuelven el estado de la respuesta como parte de su anatomía. Esto lo realizan a través de diferentes códigos de respuesta que están estandarizados por documentos llamados RFC. IANA (2021) nos indica los siguientes códigos de respuesta:

Figura 4. Nomenclatura de códigos de respuesta de peticiones HTTP.

Los códigos más utilizados son los siguientes:

Figura 5. Principales códigos de respuesta de peticiones HTTP.

Manejo de los recursos de origen cruzado (CORS)

CORS es el acrónimo de cross-origin resource sharing, es decir, intercambio de recursos de origen cruzado y “es una característica de seguridad del navegador que restringe las solicitudes HTTP de origen cruzado que se inician desde secuencias de comandos que se ejecutan en el navegador” (AWS, 2021).

Esto quiere decir que tu API REST está protegida porque se validará desde qué dominio se realizó la petición y si no está en la lista de orígenes permitidos, no regresará una respuesta satisfactoria.

Spring nos permite hacer la configuración de los CORS a nivel método, que tiene la ventaja de que por cada petición se habilite un permiso especial. Para configurar los CORS a nivel método se ocupa la anotación @CrossOrigin.

@CrossOrigin(origins = http://localhost:8080

@GetMapping(“/getNombre)

public ResponseEntity<?> obtenerTodos() {

 //…

}

Tabla 2. Configuración de CORS a nivel método.

Asimismo, se puede configurar a nivel global para que funcione en toda nuestra aplicación.

@Bean

public WebMvcConfigurer CorsConfig() {

 return new WebMvcConfigurer() {

 @Override

 public void agregarCors(CorsRegistry registro) {

        registro.addMapping(“/api/**”)

        allowedOrigins(“http://localhost:8080”)

        allowedMethods(“GET”, “POST”, “PUT”, “DELETE”)

        maxAge(3600);                            }

  };

}

 Tabla 3. Configuración de CORS a nivel global.

Acceso a datos con JPA y seguridad

La persistencia de datos “es un medio mediante el cual una aplicación puede recuperar información desde un sistema a de almacenamiento no volátil y hacer que esta persista” (IBM, 2021). La persistencia de datos es útil para las aplicaciones que requieren el uso de bases de datos relacionales y JPA (Java Persistance API) permite que exista una relación entre las tablas de la base de datos y los objetos en Java; hace uso de anotaciones para lograr su objetivo.

IBM (20201) menciona que un proveedor de persistencia JPA utiliza los elementos siguientes para habilitar una gestión de persistencia m   ás fácil en un entorno EJB 3.x:

Figura 6. Elementos de un proveedor de persistencia JPA.

Desde la página de:

spring initializr: https://start.spring.io/

El siguiente enlace es externo a la Universidad Tecmilenio,
al acceder a éste considera que debes apegarte a sus términos y condiciones.

Puedes generar un nuevo proyecto de Spring Boot con JPA agregando las dependencias que te servirán para conectar la base de datos.

Figura 7. Spring initializr.
Esta pantalla se obtuvo directamente del sitio que se está explicando en la computadora, para fines educativos.

Una vez que se tiene el proyecto creado, se descargan las dependencias de Maven a través del comando:

> mvn clean install

Tabla 4. Comando para instalar dependencias Maven.

Para realizar elmapeo de tablas como clases, es decir, relacionar cada campo de la tabla con un atributo de la clase, debes hacerlo mediante la anotación @Entity que sirve para indicar que la clase se relacionará a una tabla en tu base de datos. Por ejemplo, cuando tienes una tabla en la base de datos llamada “clientes” representada de la siguiente forma:

Tabla 5. Representación de entidad clientes.

Lo puedes mapear de la siguiente forma:

import javax.persistence.*;

import java.io.Serializable;

import java.util.Date;

 

@Entity

@Table(name="alumnos")

public class Alumnos implements Serializable {

 @Id

 @GeneratedValue(strategy= GenerationType.IDENTITY)

 private Long id;

 

 private String nombre;

 private String email;

 private String rol;

 

 private static final long SerialVersionUID = 1L;

 

}

Tabla 6. Ejemplo de mapeo de una tabla como clase en Spring.

Del ejemplo anterior se creó una Entity con llave primaria, ya que se colocaron las anotaciones @Id y @GeneratedValue para especificar que el atributo id es la llave primaria.

Para realizar el mapeo de relaciones entre clases se realizan con las anotaciones:

Figura 8. Anotaciones para mapeo de relaciones.

Entonces, siguiendo con nuestro ejemplo anterior, relacionaremos la tabla “clientes” con la tabla “ordenes” para indicar que tienen una relación a través de la llave primaria id en la tabla clientes.

package com.ejemplo.jpa.models.entity;

 

import javax.persistence.*;

import java.io.Serializable;

 

@Entity

@Table(name="ordenes")

public class Orden implements Serializable {

 @Id

 @GeneratedValue(strategy = GenerationType.IDENTITY)

 private Long id;

 private String code;

 private Double total;

 

 @ManyToOne(fetch = FetchType.LAZY,cascade = CascadeType.ALL)

 @JoinColumn(name = "id",foreignKey = @ForeignKey(name = "id"))

 private Cliente cliente;

}

Tabla 7. Ejemplo de mapeo de relaciones entre clases.

Una vez mapeadas nuestras tablas y relaciones, podemos realizar un CRUD y lo podemos realizar por medio de la interfaz CRUD Repository que nos proporciona métodos para realizar las principales operaciones.

package com.ejemplo.jpa.models.entity;

 

public interface ClienteRepository extends CrudRepository<Cliente, Long> {

 

 Cliente findAll();

 

 }

Tabla 8. Ejemplo de aplicación de interfaz CRUD Repository.

En caso de que los métodos implementados en la interfaz CRUD Repository no cumplan con los criterios de tus búsquedas, puedes implementar QueryMethods que insertan código en SQL para facilitar tus operaciones.

package com.ejemplo.jpa.models.entity;

 

public interface AlumnoRepository extends CrudRepository<Alumno, Long> {

 

 @Query("select c from alumnos c where c.email = :email")

 Alumno buscarporEmail(String email);

 

 @Query("select c from alumnos c where c.id = :id")

 Alumno buscarporId(Long id);

 

 }

Tabla 9. Ejemplo de uso de QueryMethods.

Seguridad con Json Web Token (JWT)

La seguridad de un API es muy importante, es por lo que la Spring aplica la seguridad de muchas formas, entre ellas el uso de bibliotecas como JWT.

JSON Web Token (JWT) “es un estándar abierto (RFC 7519) que define una forma compacta y autónoma de transmitir información de forma segura entre las partes como un objeto JSON” (JWT, 2021). La estructura de JWT está encriptada en base 64.

Figura 9. Página web de JWT.
Esta pantalla se obtuvo directamente del sitio que se está explicando en la computadora, para fines educativos.

Es importante que comprendas la diferencia entre los conceptos de autenticación y autorización. Autenticación es el proceso en el que el cliente valida su identidad, por ejemplo, cuando realiza un login. Autorización es el proceso en el que se valida si el cliente cuenta con los permisos para acceder a un determinado recurso.

Para la generación del JWT en Spring lo hacemos mediante el uso de las dependencias spring-security-test y jjwt, por lo que, al crear tu proyecto en Spring, debería tener contener el siguiente código entre las dependencias del archivo pom.xml:

<dependency>
 <groupId>org.springframework.security</groupId>
 <artifactId>spring-security-test</artifactId>
 <scope>test</scope>
 </dependency>
 <!-- JSON WEB TOKEN -->
 <dependency>
 <groupId>io.jsonwebtoken</groupId>
 <artifactId>jjwt</artifactId>
 <version>0.9.0</version>

 </dependency>

Tabla 10. Ejemplo de dependencias necesarias para implementar JWT en Spring.

Como Spring trabaja con el patrón de diseño MVC, debemos crear un controlador que para manejar todos los endpoints:

@RestController

public class LoginController {

 

 @RequestMapping("login")

 public String login(@RequestParam(value="name", defaultValue="Tecmilenio") String name) {

 return "Usuario: "+name+"!!";

 }

}

Tabla 11. Ejemplo de controlador en Spring.

Para definir los datos de usuario, crearemos la clase “User” que nos servirá como DTO.

public class Usuario{

 private String usuario;

 private String password;

 private String tokenjwt;

 

 

 public String getUsuario() {

 return this.usuario;

 }

 

 public void setUsuario(String usuario) {

 this.usuario = usuario;

 }

 

 public String getPassword() {

 return this.password;

 }

 

 public void setPassword(String password) {

 this.password = password;

 }

 

 public String getTokenjwt() {

 return this.tokenjwt;

 }

 

 public void setTokenjwt(String tokenjwt) {

 this.tokenjwt = tokenjwt;

 }

 

}

Tabla 12. Clase DTO.

De igual forma, creamos otro controlador que tendrá toda la información para generar el JWT. De esta forma implementaremos la autenticación.

@RestController

public class UsuarioController {

 

 @PostMapping("usuario")

 public User login(@RequestParam("usuario") String usuario, @RequestParam("password") String pass) {

 

 String token = obtenerToken(usuario);

 User user = new User();

 user.setUser(usuario);

 user.setToken(token);

 return user;

 

 }

 

 private String obtenerToken(String usuario) {

 String secretKey = "topSecretKey";

 List<GrantedAuthority> grantedAuthorities = AuthorityUtils

 .commaSeparatedStringToAuthorityList("ROL");

 String token = Jwts

 .builder()

 .setId("JWT")

 .setSubject(usuario)

 .claim("authorities",

 grantedAuthorities.stream()

 .map(GrantedAuthority::getAuthority)

 .collect(Collectors.toList()))

 .setIssuedAt(new Date(System.currentTimeMillis()))

 .setExpiration(new Date(System.currentTimeMillis() + 600000))

 .signWith(SignatureAlgorithm.HS512,

 secretKey.getBytes()).compact();

 

 return "Bearer " + token;

 }

}

Tabla 13. Ejemplo de clase controller que genera el JWT.

También necesitarás una clase que será la clase principal en donde habilitaremos la seguridad por medio de la anotación @EnableWebSecurity.

@SpringBootApplication

public class TokenDemo {

 

 public static void main(String[] args) {

 SpringApplication.run(TokenDemo.class, args);

 }

 

 @EnableWebSecurity

 @Configuration

 class WebSecurityConfig extends WebSecurityConfigurerAdapter {

 

 @Override

 protected void configure(HttpSecurity httpSec) throws Exception {

 httpSec.csrf().disable()

 .addFilterAfter(new JWTAuthorizationFilter(), UsernamePasswordAuthenticationFilter.class)

 .authorizeRequests()

 .antMatchers(HttpMethod.POST, "/auth/user").permitAll()

 .anyRequest().authenticated();

 }

 }

 

}

Tabla 14. Ejemplo de clase principal de implementación de JWT.

Hasta el momento has bloqueado todas las peticiones que se realicen si no cuentan con token, pero debes habilitar la petición cuando sí exista un token, es decir, implementaremos la autorización.

public class JWTFiltro extends OncePerRequestFilter {

 

 private final String _HEADER = "Authorization";

 private final String _PREFIX = "Bearer ";

 private final String _SECRET = "cadenaSecretaQueDebesCambiar";

 

 @Override

 protected void filtrar(HttpServletRequest request, HttpServletResponse response, FilterChain chain) throws ServletException, IOException {

 try {

 if (revisarToken(request, response)) {

 Claims claims = validarToken(request);

 if (claims.get("authorities") != null) {

 autenticar(claims);

 } else {

 SecurityContextHolder.clearContext();

 }

 } else {

 SecurityContextHolder.clearContext();

 }

 chain.doFilter(request, response);

 } catch (ExpiredJwtException | UnsupportedJwtException | MalformedJwtException e) {

 response.setStatus(HttpServletResponse.SC_FORBIDDEN);

 ((HttpServletResponse) response).sendError(HttpServletResponse.SC_FORBIDDEN, e.getMessage());

 return;

 }

 }

 

 private Claims validarToken(HttpServletRequest request) {

 String token = request.getHeader(_HEADER).replace(_PREFIX, "");

 return Jwts.parser().setSigningKey(_SECRET.getBytes()).parseClaimsJws(token).getBody();

 }

 

 private void autenticar(Claims claims) {

 @SuppressWarnings("unchecked")

 List<String> authorities = (List<String>) claims.get("authorities");

 

 UsernamePasswordAuthenticationToken auth = new UsernamePasswordAuthenticationToken(claims.getSubject(), null,

 authorities.stream().map(SimpleGrantedAuthority::new).collect(Collectors.toList()));

 SecurityContextHolder.getContext().setAuthentication(auth);

 

 }

 

 private boolean revisarToken(HttpServletRequest request, HttpServletResponse res) {

 String authenticationHeader = request.getHeader(_HEADER);

 if (authenticationHeader == null || !authenticationHeader.startsWith(_PREFIX))

 return false;

 return true;

 }

 

 }

Tabla 15. Clase de autorización con JWT.

Esta clase comprobará si hay un token y, en caso de que exista, lo desencriptará y validará para emitir la autorización a la petición.


Cierre

El uso de las API REST es muy común hoy en día para conectar una base de datos con diferentes dispositivos a través de Internet. Con su uso, se garantiza el intercambio de mensajes en un formato establecido y con la seguridad que se requiere. Las operaciones más importantes que se realizan en un CRUD pueden ser obtenidas con los verbos HTTP.

Figura 10. Comparación entre verbos HTTP y CRUD.

Empresas y organizaciones a nivel mundial se han beneficiado con su uso porque pueden ahorrar tiempo de desarrollo, pueden agilizar los procesos y minimizan los costos de mantenimiento y actualización en una aplicación. Por otra parte, al realizar una separación entre el servidor y el cliente, las aplicaciones se hacen más rápidas y amigables para el usuario.


Checkpoint

Asegúrate de:

  • Comprender la importancia y el funcionamiento de la API REST para integrarlas en el desarrollo web y/o móvil.
  • Implementar un CRUD con API REST para realizar una integración de una base de datos con una aplicación.
  • Distinguir entre autenticación y autorización para aplicar los conceptos en el desarrollo de una aplicación web segura.

Bibliografía

  • AWS. (2021). Amazon API Gateway. Recuperado de https://docs.aws.amazon.com/es_es/apigateway/latest/developerguide/apigateway-dg.pdf
  • Gaitatzis, T. (2019). Learn REST APIs (2a ed.). Estados Unidos: BackupBrain Press.
  • Iana. (2021). Hypertext Transfer Protocol (HTTP) Status Code Registry. Recuperado de https://www.iana.org/assignments/http-status-codes/http-status-codes.xhtml#http-status-codes-1
  • IBM. (2021). JPA (Java Persistence API). Recuperado de https://www.ibm.com/docs/es/was-liberty/nd?topic=overview-java-persistence-api-jpa
  • JWT. (s.f.). Introduction to JSON Web Tokens. Recuperado de https://jwt.io/introduction
  • MDN Web Docs. (s.f.). Métodos de petición HTTP. Recuperado de https://developer.mozilla.org/es/docs/Web/HTTP/Methods

La obra presentada es propiedad de ENSEÑANZA E INVESTIGACIÓN SUPERIOR A.C. (UNIVERSIDAD TECMILENIO), protegida por la Ley Federal de Derecho de Autor; la alteración o deformación de una obra, así como su reproducción, exhibición o ejecución pública sin el consentimiento de su autor y titular de los derechos correspondientes es constitutivo de un delito tipificado en la Ley Federal de Derechos de Autor, así como en las Leyes Internacionales de Derecho de Autor.

El uso de imágenes, fragmentos de videos, fragmentos de eventos culturales, programas y demás material que sea objeto de protección de los derechos de autor, es exclusivamente para fines educativos e informativos, y cualquier uso distinto como el lucro, reproducción, edición o modificación, será perseguido y sancionado por UNIVERSIDAD TECMILENIO.

Queda prohibido copiar, reproducir, distribuir, publicar, transmitir, difundir, o en cualquier modo explotar cualquier parte de esta obra sin la autorización previa por escrito de UNIVERSIDAD TECMILENIO. Sin embargo, usted podrá bajar material a su computadora personal para uso exclusivamente personal o educacional y no comercial limitado a una copia por página. No se podrá remover o alterar de la copia ninguna leyenda de Derechos de Autor o la que manifieste la autoría del material.