Securizar un API REST utilizando JSON Web Tokens

Índice de contenidos

1. Introducción

En este tutorial veremos cómo securizar un API REST empleando JSON Web Tokens (JWT). Para este tutorial utilizaremos un API muy simple donde activaremos Spring Security con la configuración adecuada e implementaremos las clases necesarias para el uso de JWT.

En anteriores tutoriales vimos con securizar un API REST utilizando Node.js y JWT, en esta ocasion utilizaremos Spring Boot ya que nos permite desarrollar rápidamente API REST con el mínimo código y por tanto con el menor número de errores 😉

Disponéis de un tutorial de Natalia Roales en el portal sobre proyectos con Spring Boot y os dejamos el código fuente de este tutorial en github para vuestra consulta.

2. Entorno

El tutorial está escrito usando el siguiente entorno:

  • Hardware: Portátil Mac Book Pro 15″ (2,5 Ghz Intel Core i7, 16 GB DDR3)
  • Sistema Operativo: Mac OS Sierra 10.12.6
  • Entorno de desarrollo: Spring Tool Suite 3.9.0

3. JSON Web Token

JSON Web Token (JWT) es un estándar abierto (RFC 7519) que define un modo compacto y autónomo para transmitir de forma segura la información entre las partes como un objeto JSON. Esta información puede ser verificada y es confiable porque está firmada digitalmente. Los JWT se pueden firmar usando un secreto (con el algoritmo HMAC) o utilizando un par de claves públicas / privadas usando RSA.

3.1. Ventajas de los tokens frente a las cookies

Quizás la mayor ventaja de los tokens sobre las cookies es el hecho de que no tenga estado (stateless). El backend no necesita mantener un registro de los tokens. Cada token es compacto y auto contenido. Contiene todos los datos necesarios para comprobar su validez, así como la información del usuario para las diferentes peticiones.

El único trabajo del servidor consiste en firmar tokens al iniciar la sesión y verificar que los tokens intercambiados sean válidos. Esta característica permite la escalabilidad inmediata ya que las peticiones no dependen unas de otras. De esta forma se pueden tramitar en diferentes servidores de forma autónoma.

Las cookies funcionan bien con dominios y subdominios únicos, pero cuando se trata de administrar cookies en diferentes dominios, puede volverse complejo. El enfoque basado en tokens con Cross Origin Resource Sharing (CORS) habilitado hace trivial exponer las API a diferentes servicios y dominios.

Con un enfoque basado en cookies, simplemente se almacena el ID de sesión en una cookie. JWT por otro lado permiten almacenar cualquier tipo de metadatos, siempre y cuando sea un JSON válido.

Si os preguntáis por el rendimiento, cuando se utiliza la autenticación basada en cookies, el backend tiene que hacer una búsqueda habitualmente una base de datos para recuperar la información del usuario, esto seguramente supera el tiempo que pueda tomar la decodificación de un token. Además, puesto que se pueden almacenar datos adicionales dentro del JWT, por ejemplo, permisos de usuario, puede ahorrarse llamadas adicionales para la búsqueda de esta información.

Recordad que el token habitualmente va firmado pero no va cifrado. En la web de jwt.io disponéis de un depurador que permite consultar y comprobar la validez del mismo.

Después de tanta teoría vamos a lo interesante.

4. Estructura del proyecto

Nuestro proyecto a nivel de Maven está configurado para ser un proyecto Spring Boot de tipo web, que utiliza Spring Security, JPA y HSQLDB (podéis consultar el pom.xml). Veamos las partes más importantes del ejemplo, consta de un controlador para acceso al API REST, el acceso a la capa de datos por JPA y los beans de dominio, muy simple. Para esta demostración utilizaremos la base de datos en memoria HSQLDB (HyperSQL Database).

A nivel de controlador se disponen de métodos para crear un usuario, consultar todos o consultar uno concreto. Para evitar almacenar las password en plano, aplicamos una función de Hash basada en el cifrado Blowfish (BCrypt). Recordad que el uso de MD5 para almacenar las password no se recomienda, utilizad algoritmos más modernos.

UsuarioController.java

5. Spring Security

Gracias a Spring Security podemos incorporar mecanismos potentes para proteger nuestras aplicaciones utilizando una cantidad mínima de código.

En nuestro caso indicamos a Spring Security que proteja todas las URLs excepto la URL de login, así mismo, declaramos las implementaciones que utilizaremos para realizar la autenticación y autorización.

WebSecurity.java

Podemos observar que se ajusta la configuración para CORS y se desactiva el filtro de Cross-site request forgery (CSRF). Esto nos permite habilitar el API para cualquier dominio, esta es una de las grandes ventajas del uso de JWT.

6. Implementación

En el siguiente diagrama podéis observar el flujo habitual de una aplicación securizada. Si lo comparamos al flujo seguido en la autenticación vía cookies, es muy similar.

Por fin llegamos a la lógica de negocio a utilizar para autenticar y autorizar nuestras peticiones. Para simplificar este tutorial, se verificará únicamente que exista el usuario y password en nuestra base de datos. Se podría incorporar un modelo más complejo incorporando permisos y roles pero se aleja del objetivo de este tutorial. Si os interesa estos temas, podéis consultar su manejo en la documentación de Spring Security.

6.1. Autenticación

Haciendo uso de las clases proporcionadas por Spring Security, extendemos su comportamiento para reflejar nuestras necesidades. Se verifica que las credencias proporcionadas son válidas y se genera el JWT.

JWTAuthenticationFilter.java

No hay obligación de devolver el token en la cabecera ni con una clave concreta pero se recomienda seguir los estándares utilizados en la actualidad (RFC 2616, RFC 6750). Lo habitual es devolverlo en la cabecera HTTP utilizando la clave “Authorization” e indicando que el valor es un token “Bearer “ + token

Este token lo deberá conservar vuestro cliente web en su localstorage y remitirlo en las peticiones posteriores que se hagan al API.

6.2. Autorización

La clase responsable de la autorización verifica la cabecera en busca de un token, se verifica el token y se extrae la información del mismo para establecer la identidad del usuario dentro del contexto de seguridad de la aplicación. No se requieren accesos adicionales a BD ya que al estar firmado digitalmente si hay alguna alteración en el token se corrompe.

JWTAuthenticationFilter.java

7. Probando…probando

Para las pruebas seguiremos el flujo que vimos previamente, primero, se invoca al login para recuperar el token y posteriormente invocaremos las llamadas al API utilizando el token obtenido.

Una vez arrancamos la aplicación y lanzamos los comandos obtendremos algo parecido a la imagen adjunta. Si intentamos invocar alguna URL sin el token obtendremos un código de error HTTP 403.

8. Conclusiones

La securización de API es un tema muy extenso y hemos visto una pequeña parte en este tutorial. Como habréis podido observar Spring nos facilita la incorporación de JWT a nuestras APIs gracias a su “magia”. En el caso que no dispongáis de esta posibilidad, os animo a consultar los frameworks y librerías existentes ya que cada vez está más extendido el uso de los tokens para diferentes lenguajes.

Espero que os haya servido

9. Referencias