El desembarco de los micros servicios como una nueva forma de pensar nuestras aplicaciones nos ha enfrentado a nuevos desafíos a la hora de securizar nuestras Api, Oauth 2.0 nos da la receta para hacer esta tarea mucho más sencilla y Spring Security nos provee el soporte para implementar nuestras soluciones utilizando estas recetas sin tener que reinventar la rueda.
EL objetivo de este post es mostrar como securizar un Api rest echa en java con Spring Security y Oauth2 de forma simple y sencilla.
Para esto vamos a asumir conocimientos básicos sobre Spring Security y vamos a hacer un breve repaso sobre Oauth2.0 y los flujos de autenticación que propone.
¿Qué es Oauth 2.0?
Oauth 2.0 es un estándar abierto para la autorización de Apis, que nos permite compartir información entre sitios sin tener que compartir la identidad. Es un mecanismo utilizado por grandes compañías como Google, Facebook, Microsoft, Twitter, GitHub.
¿Como funciona?
Funciona delegando la autenticación del usuario a otro servicio que aloja su cuenta de usuario. Siendo este es el encargado de autorizar a las aplicaciones de terceros.
¿Qué Problema viene a resolver?
Oauth 2.0 surge para dar solución a la necesidad del envío continuo de credenciales entre servidor y cliente.
Supongamos que tenemos una Api rest y un cliente que consume de la misma. Antes de Oauth 2.0, para que exista un mínimo de seguridad el usuario debía enviar sus credenciales. Si la aplicación cliente y el api rest es desarrollada por nosotros posiblemente no exista ningún problema. El problema surge cuando se quiere hacer una integración con aplicaciones de terceros.
Por ejemplo. Supongamos que nuestra aplicación necesita integrarse con Twitter y hacer posteos en nombre del usuario. ¿qué deberíamos hacer? ¿pedirle usuario y la contraseña de Twitter al usuario?
Parece no ser un camino muy seguro para el usuario. Con Oauth 2.0 el usuario delega la capacidad de hacer ciertas acciones en su nombre sin tener que almacenar usuario y contraseña de Twitter.
Roles
Cliente (Client):
El cliente es la aplicación que quiere acceder a una cuenta de un usuario. A fin de conseguir esto, debe contar con autorización del usuario, y esta autorización se debe validar (a través de la API del servicio).
El cliente puede ser tanto una aplicación web como una app Mobile, una aplicación de escritorio, una aplicación para Smart tv u otro Api.
Propietario del recurso (Owner):
Es el usuario que da la autorización a la aplicación para acceder a su cuenta. Eventualmente podría decidir el alcance (scope) de la autorización que sería lo que el usuario permite hacer en su nombre a la aplicación autorizada.
Servidor de recursos(Resources server):
Es nuestra Api. Es donde se almacenan los recursos a los que queremos proteger.
Servidor de autenticación (Autentication server):
Es el responsable de gestionar las peticiones de autorización. Verifica la identidad de los usuarios y emite un token (access token) a la aplicación cliente. En muchos casos esta implementado por un tercero conocido (Facebook, Google, Twitter, etc.) pero existe varias formas de implementarlo. Inclusive podría formar parte del servidor de recursos.
Los tokens e identificadores que se manejan dentro de OAuth 2.0 son:
El client id es un identificador único que representa la aplicación en el servidor de autenticación.
El client secret es una clave secreta que pertenece a la aplicación que es utilizada por el servidor de autorización para comprobar si dicha aplicación está autorizada.
Los access token: son claves proporcionadas por el servidor de autorización a la aplicación que permite el acceso a las APIs durante un tiempo limitado tras el cual el token deja de ser válido.
Los refresh token: es una clave también proporcionada por el servidor de autorización a la aplicación que permite solicitar nuevos access token después de que estos hayan caducado.
Flujos o grant types
Los flujos de OAuth también denominados grant types hacen referencia al modo en que una aplicación obtiene un access token que le permite acceder a los datos expuestos a través de una API.
La existencia de estos flujos por parte del estándar surge para dar respuesta a todos los escenarios de negocio que se pueden presentar en el consumo de las APIs :
Client credentials grant type
Este flujo representa el caso en el que la aplicación pertenece al usuario, por lo que no hay necesidad de que éste se autentique ni ayude de forma alguna al servidor de autenticación a decidir si el acceso para esa aplicación está garantizado, es decir, el access token que envía el servidor se obtiene autenticando sólo a la aplicación, no al usuario.
La principal diferencia con el resto del flujos es que el usuario no interactúa en el proceso, por lo que la aplicación no puede actuar en nombre del usuario, solo en nombre propio.
Resource Owner Password grant type
Este flujo se caracteriza por la intervención por parte del usuario proporcionando su usuario y contraseña en la aplicación cliente, no en el servidor de autenticación. Esta situación implica que los dueños del recurso deben confiar plenamente en la aplicación para introducir sus credenciales.
Por ejemplo, en Spotify te solicitan para iniciar sesión las credenciales de Facebook directamente en su aplicación. Si no fuera una aplicación confiable los usuarios no pondrían sus datos. Spotify es suficientemente confiable y garantiza que ni guarda ni modifica la contraseña de Facebook.
Pero, ¿no se supone que el hecho de proporcionar las contraseñas era lo que OAuth 2.0 pretendía evitar? Cierto, pero el uso de este flujo aprovecha el resto de beneficios de OAuth como son el uso de access tokens con una vida útil limitada.
Por ejemplo, si tenemos una aplicación móvil en lugar de almacenar el usuario y contraseña del usuario, la aplicación solicita los datos en el momento de la instalación, de forma posterior solo tiene que hacer uso del access token.
Authorization code grant type
Este flujo se caracteriza por la intervención del usuario para autorizar de forma explícita el acceso a sus datos por parte de la aplicación y por el uso de un código proporcionado por el servidor de autenticación para que la aplicación pueda obtener un access token.
Implenetacion con Spring Security
En este ejemplo vamos a securizar nuestra Api rest implementando el flujo Resource Owner Password grant type, que nos permitirá securizar los endpoint de nuestra Api permitiendo ser consumida por diferentes tipos de clientes (una pagina web, otra Api, Postman,etc).
Para explorar más sobre el soporte que da Spring Security a Oauth 2.0 vamos a implementar nuestro propio servidor de autenticación, pero cabe aclarar que se podría utilizar un servicio de autenticación de terceros.
Implementación del servidor de autenticación con Spring Security:
Como mencionamos antes la idea de tener un Servidor de autenticación es tener un servidor que verifica la identidad de los usuarios y emite access tokens a la aplicación cliente.
Esto lo vamos a implementar a través de una Api rest que expone 2 endpoint:
POST /oauth/token devuelve un access token si se verifica la identidad del usuario cliente que pasa sus credenciales como parámetro en el request.
POST /oauth/check_token valida un access token pasado como parámetro
Lo primero que debemos hacer es un proyecto Spring Boot con las siguientes dependencias.
Una vez creado el proyecto Spring Boot podemos pasar a agregar las clases de configuración
Creamos la clase AuthorizacionServerConfiguration que extiende de AuthorizationServerConfigurerAdapter y acá viene la parte interesante:
Sobrescribiendo el método configure podemos indicarle a Spring Security, el UserDetailService que queremos que use.
En este ejemplo se implementó un UserDetailService custom en el que vamos a guardar la información necesaria
Oauth2 autentica las aplicaciones cliente para algunos tipos de acceso sobre la información del usuario.
En este ejemplo, se configura una aplicación cliente con nombre cliente.
inMemory Significa que todos los datos necesarios para crear una sesión serán almacenados en la memoria. Cuando reinicie su aplicación, todos los datos de la sesión desaparecerán, lo que significa que los usuarios deben iniciar sesión y autenticarse nuevamente.
Esto puede mejorarse implementando un customDetailService para persistir los datos de las aplicaciones clientes, pero queda fuera del alcance de este post por lo que nuestra implementación será en memoria.
Los tipos de concesión representan los derechos de la aplicación cliente sobre la información del usuario. En este caso, la aplicación cliente tiene derechos para leer y escribir.
Con este Bean indicamos a Spring Security que algoritmo de codificación que se va a usar es Bcript.
Con este Bean indicamos de qué manera se almacenarán los tokens en la aplicación, en nuestro ejemplo lo haremos con InMemoryTokenStore que nos permite guardar los tokens en memoria, pero podríamos implementar nuestro propio TokenStore y persistir los tokens para evitar que ante un eventual reinicio del servidor de autenticación los usuarios tengan que volver a autenticarse.
Por último, es necesario agregar la annotation @EnableResourceServer a la clase principal del proyecto.
Ya tenemos nuestro servidor de autenticación configurado y podemos empezar probarlo con Postman.
Para esto le damos run a la clase principal del proyecto que en nuestro ejemplo escuchara en el puerto 8081 y enviamos un request Post al enpoint /oauth/token que Spring Security provee por defecto para autenticar a un usuario.
Para poder autenticar al usuario y recibir un access token se deben enviar las credenciales del usuario como parámetro en el body (password y username) y adicionalmente el de flujo de autenticación con el que se va a autenticar.
Y por último el client id y client secret como se indica en la imagen.
Si las credenciales del usuario son válidas este request retornará una access token con el que podremos tener acceso a los recursos protegidos de nuestro Api.
Configuración de mi Api para securizar sus endpoint:
Para realizar esto basta con agregar las siguientes dependencias en el pon de nuestra Api.
Luego con la annotation @EnableResourceServer indicamos a Spring Security que la Api cumple el rol de Servidor de recurso.
Y por último creamos una clase de configuración a la que llamaremos ResourceServerEndpointConfig que extiende de ResourceServerConfigurerAdapter, aqui indicaremos los enpoint que queremos securizar de la misma manera que lo hacemos siempre con Spring Security. En este ejemplo tenemos un endpoint /message que devuelve el mensaje “secret message” de estar autorizado.
Y también declararemos un Bean donde indicaremos la Url donde se encuentra alojado el servidor de autenticación y apuntaremos al endpiont que va a validar los tokens que recibamos para acceder a los recursos que protegemos.
Para hacer un seguimiento de este flujo de autenticación podemos probar nuestro enpoint securizado con Postman enviando el token anteriormente obtenido como paramento de autenticación Barrer token.
Nuestra Api responde efectivamente como esperábamos. Ya que el token enviado es validado contra el servidor de autenticación y este verifica que es un token emitido por él. Paso siguiente nuestra Api nos habilita el acceso al recurso consultado.
Eso es todo por esta ocasión espero, espero que les gustado. Saludos y hasta el próximo post.