Securizando un API Rest con JWT y roles

2
5335

Índice de contenidos

  1. Introducción
  2. Entorno
  3. Securizando con roles: diferencia entre @PreAuthorize y @Secured
  4. Vamos al lío
  5. La hora de la verdad
  6. Conclusiones

1. Introducción

Siguiendo la línea de lo que nos contó Álvaro en su tutorial sobre como securizar un API Rest con JWT, vamos a profundizar un poco más en lo que nos ofrece Spring Security para poder proteger nuestra API permitiendo el acceso según los roles asociados a los usuarios.

Como la base es el tutorial anterior, no volveremos a hacer hincapié en lo que Álvaro nos enseñó y lo tomaremos como punto de partida para este tutorial. El ejemplo completo puedes encontrarlo en GitHub.

2. Entorno

  • Hardware: Portátil MacBook Pro 15 pulgadas (2.5 GHz Intel i7, 16GB 1600 Mhz DDR3, 500GB Flash Storage).
  • Sistema Operativo: MacOs Mojave 10.14.2
  • Versiones del software:
    • Java 11
    • Spring Boot 2.1.0.RELEASE
    • Lombok 1.18.4

3. Securizando con roles: diferencia entre @PreAuthorize y @Secured

Los roles nos permiten tener un control más especifico del acceso a nuestra API, pudiendo acotar a un grupo más reducido de usuarios ciertos recursos de nuestro sistema o proteger el acceso a operaciones sensibles o críticas. Por ejemplo, si tenemos una intranet para dar de alta empleados, podríamos permitir el acceso a las altas únicamente a los responsables de RRHH y la consulta de empleados a un perfil más público (como los empleados de la empresa).

Spring Security nos permite de manera sencilla proteger nuestros puntos de API con las etiquetas @PreAuthorize y @Secured indicando los roles que tienen acceso. En función de cómo queramos securizar nuestros endpoints, utilizaremos una anotación u otra:

  • @PreAuthorize es una anotación más nueva que @Secured (disponible desde la versión 3 de Spring Security) y mucho más flexible.
  • @PreAuthorize soporta Spring Expression Language (SpEL) pudiendo acceder a todos métodos y propiedades de la clase SecurityExpressionRoot pudiendo por tanto utilizar expresiones como hasRole, permitAll…mientras que con @Secured indicamos sólo los roles permitidos.
  • Cuando securizamos nuestros endpoints con @Secured indicando varios roles se permite el acceso a cualquier usuario que tenga asociado al menos uno de los roles (equivalente a una expresión OR). Con @PreAuthorize, como admite expresiones (pudiendo usar AND, OR, NOT) podríamos implementar un acceso exclusivo a aquellos usuarios que tengan varios roles asociados:
    • @Secured({«ROLE_ADMIN», «ROLE_USER»}) Acceso a todos usuarios con rol admin o user.
    • @PreAuthorize(«hasRole(‘ROLE_ADMIN’) AND hasRole(‘ROLE_USER’)») Acceso a todos usuarios con rol admin y user.
    • @Secured({«ROLE_ADMIN»}) y @PreAuthorize(«hasRole(‘ROLE_ADMIN’)») son equivalentes.

4. Vamos al lío

4.1 Implementando la API de usuarios

A continuación vamos a ver qué cambios tenemos que hacer en nuestra configuración de Spring Security para securizar nuestra aplicación con roles. La aplicación desarrollada inicializa la base de datos con una serie de roles (admin, user y operational), una serie de usuarios y la configuración de qué roles tienen asociados cada uno de estos usuarios:

  • user1/password1: rol admin y rol usuario
  • user2/password2: rol operational
  • user3/password3: rol usuario

Nuestra aplicación tiene disponible varios endpoints:

  • Accesible para rol usuario:
    • [GET] /users/{id}: Recuperar la información de un usuario por id.
  • Accesible para rol administrador:
    • [POST] /users: Dar de alta usuarios.
    • [GET] /users/{id}: Recuperar la información de un usuario por id.

4.2 Configurando @PreAuthorized

Lo primero que tenemos que hacer es configurar dentro de nuestro WebSecurityConfig que se pueda utilizar el método PreAuthorized para la seguridad:

Ahora, protegemos nuestros endpoints para los roles indicados anteriormente, teniendo en cuenta que recuperar el detalle de un usuario estará disponible para para rol admin y user:

4.3 Añadiendo los roles al token

Para finalizar, nos falta añadir la información de los roles en el token generado para el usuario. En primer lugar, la instancia de UserDetails que contiene la información del usuario autenticado debe contener los authorities asociados:

Los roles asociados vendrán informados con un prefijo ROLE_ (es buena práctica hacerlo por legibilidad) seguido del rol definido en nuestra base de datos.

De este modo, la instancia de Authentication a la que está asociada el usuario y que maneja la seguridad ya tiene los roles configurados en nuestro sistema, pudiendo devolver el token con esta información dentro de la propiedad claim:

Ahora nuestro token ya tiene esta información y podemos recuperarla para construir nuestro UsernamePasswordAuthenticationToken que es el utilizado por Spring Security para saber si tiene acceso al endpoint del servicio:

5. La hora de la verdad

Ahora podemos hacer pruebas con nuestros usuarios. Por ejemplo user1 y user3 , con rol de administrador y user respectivamente, pueden ver el detalle de un usuario. Primero obtendremos un token válido con la llamada a /login:

Usamos el token de la respuesta y hacemos una llamada al servicio para ver el detalle del usuario. Esta llamada nos devuelve una respuesta con código 200 (OK) y la información del usuario:

Si hacéis la prueba con user3, veréis que también tiene acceso al detalle de un usuario. Sin embargo con «user2» no podemos ver el detalle porque no tiene un rol válido. La respuesta será de tipo 403 (Forbidden):

Podéis hacer pruebas también con el endpoint de dar de alta un usuario, que es un poco más restrictivo porque sólo user1, al ser administrador, puede acceder a él.

6. Conclusiones

Es muy común el desarrollo de aplicaciones web que puedan ser gestionadas por usuarios con diferentes perfiles, por lo que la securización de nuestra API en función de roles es algo imprescindible si queremos restringir el acceso a ciertos recursos. Los puntos más relevantes en los que hemos profundizado han sido:

  • La securización por roles con @Secured y @PreAuthorize y la potencia de cada una de ellas, viendo cuándo se puede utilizar una u otra.
  • Informar en nuestro JWT token de las authorities/claims del usuario para autorizar el acceso a los recursos ofrecidos. También hemos visto cómo recuperar del token estos roles y cómo asociarlos a la instancia de tipo Authentication que maneja el acceso.

Podéis descargaros el ejemplo completo para jugar con la API que he implementado o implementar la vuestra propia jugando con los roles y los usuarios. Cualquier pregunta o duda que tengáis no dudéis en escribir en la sección de comentarios del tutorial. Intentaré responder lo antes posible.

2 Comentarios

Dejar respuesta

Please enter your comment!
Please enter your name here