icono_twiter icono Facebook
Carlos García Pérez

Técnico especialista en informática de empresa (CEU).

Ingeniero Técnico en Informática de Sistemas (UPM)

Creador de MobileTest, Haaala!, Girillo, toi18n.

Charla sobre desarrollo de aplicaciones en Android.

Ver todos los tutoriales del autor

Fecha de publicación del tutorial: 2012-07-30

Tutorial visitado 17.280 veces Descargar en PDF
Creación de un API REST protegido por OAuth2

Creación de un API REST protegido por OAuth2

Índice de contenidos

Introducción

En este tutorial vamos a construir una aplicación web que expone un API Rest en donde los usuarios registrados podrán usar dicho API para gestionar sus notas (alta, baja, modificaciones y consultas).

El API estará protegido por el protocolo estándar OAuth2 para que aplicaciones externas puedan consultar el API en nombre del usuario.

Sobre los API REST:

Rest es un estilo de arquitectura elegante y legible en donde se hace uso de los verbos HTTP para realizar las operaciones de alta (POST), baja (DELETE), modificación (PUT) y consulta (GET) de información. (Endpoints de la aplicacion del tutorial:)

Una finalidad que se persigue es la de facilitar el trabajo a los programadores que hacen uso de ella, de manera que se incremente su productividad.

Una buena API REST debe intentar cumplir los siguientes requisitos (obviamente además debe ser rápida y escalable):

  1. Estar bien documentada.
  2. En caso de errores notificar al programador de una forma clara y suficiente para que el pueda averiguar que ha pasado y si es culpa suya corregir el problema.
  3. Proporcionar un mecanismo de filtro o consulta de selección de resultados.
  4. Proporcionar un mecanismo de respuesta parcial, en donde el programador pueda indicar que campos de información desea obtener, normalmente para ahorrar ancho de banda e incrementar velocidad en consultas desde dispositivos con recursos limitados.
  5. Estar versionado, para no afectar en futuras versiones a aplicaciones que usen versiones más antiguas del API.
  6. Ofecer un mecanismo de selección del formato de los datos de salida (normalmente XML o JSON).

En este tutorial cubriremos los puntos: 2, 3, 4 y 5.

El punto 1 está documentado pero en los comentarios del código fuente, faltaría una url o documento que podrían consultar los programadores).

Para conseguir el punto 6, puedes consultar el siguiente artículo Spring MVC. Servicios REST respondiendo en JSON o XML.

Sobre OAuth2:

El protocolo estándar OAuth2 permite que las aplicaciones puedan acceder de manera delimitada a los datos (u operaciones) ubicados en otro servicio o aplicación (proveedor de servicio) en nombre del usuario sin tener que tener que para ello darle las credenciales (usuario/password, etc) a esta aplicación tercera.

En la sección referencias y enlaces interesantes tenéis una excelente charla en castellano sobre OAuth2.

Este protocolo ofrece grandes posibilidades de integración entre aplicaciones, sin tener que compartir contraseñas, permitiendo además que el usuario pueda revocar privilegios a las aplicaciones cuando desee.

Anteriormente a OAuth, existian diversas soluciones propietarias para que las aplicaciones externas pudieran acceder a sus datos y/o servicios, por ejemplo, Google ya ofrecía ClientLogin y AuthSub.

OAuth2 se basa en el concepto de token de accesso, de manera que hay diversas formas de conseguir el token de acceso necesario para poder acceder al servicio sin tener que en ningún momento introducir las credenciales de acceso en la aplicación cliente que desea acceder a los datos. A este proceso de obtención del token de acceso se le conoce como baile OAuth2

En este tutorial se cubrirá el baile authorization_code, pues la aplicación cliente es una aplicación ubicada en un servidor (tomcat, jetty, apache, etc).

Es un tema extenso, si quereis profundizar más en el tema os recomiendo que leais el libro Getting Started With OAuth2 de O'Relly o que veais la charla en castellano que comenté previamente.

El ejemplo, la aplicación SmallNotes:

SmallNotes es una aplicación web que expone un API Rest en donde los usuarios registrados podrán usar dicho API para gestionar sus notas (alta, baja, modificaciones y consultas).

Las notas se componen de un titulo, un contenido y una serie de enlaces.

Los endpoints (urls) que expone dicho API junto con su documentación está detallado en la sección descripción de los endpoints.

Para probar el API he creado una aplicación web cliente que de consume el API que expone SmallNotes, poniéndose previamente en funcionamiento el baile OAuth2 para obtener el accessToken necesario para invocar dichos endPoints del API.

También usar el plugin RestClient para Firefox el cual además de permitirte hacer peticiones REST, hace de cliente OAuth2 sabiendo que tiene que hacer cuando recibe del servidor el error de "necesitas un accessToken" para acceder al recurso.
A continuación vemos unas capturas de pantalla de todo el ciclo:

Haz click en las imágenes para agrandarlas:

a) Configuración de OAuth2:

Configuración de OAuth2

b) Solicita al usuario que se autenticación en la aplicación SmallNotes:

OAuth2 Autenticación

c) Solitica al usuario que autorización de los
permisos que solicita acceder la aplicación externa:

OAuth2 autorización

d) Respuesta recibida:

Respuesta recibida

También puedes ejecutar la aplicación SmallNotesExternalWebApp:

a) Autenticación en la aplicación SmallNotesExternalWebApp:

b) Al hacer clic en la opción smallNotesExternalWebApp invocará mediante RestTemplate el API REST protegido por OAuth2:

Tecnologías usadas:

Para construir la aplicación he elegido las siguientes tecnologías:

  • Git:Como sistema de control de versiones que además permite un trabajo más ágil y cómodo.
  • Maven:Para la gestión de la construcción proyecto.
  • JPA2:Como framework de persistencia.
  • Liquibase:Como gestor del ciclo de vida de la base de datos, así como para introducir datos de prueba.
  • Mockito, JUnit:Para la creación de tests unitarios y/o integración.
  • MySQL y H2:Como base de datos para entorno de desarrollo y/o pruebas. (si no fuera un tutorial usaría Redis u otra solución)
  • Spring MVC:Cómo framework de desarrollo Web y su soporte REST.
  • Spring Security:Cómo framework de seguridad.
  • Spring Security OAuth2:Implementación de OAuth2 basada de Spring MVC y Spring Security.

Descripción de los endpoints:

URL: Verbo HTTP: Descripción: Resultado (JSON):
/v1/api/notes/ GET Todas las notas del usuario logado.
[{"id":1,"content":"content_1","created":1285322400000,"title":"titulo_1","links":[{"id":1,"url":"http://www.carlos-garcia.es"},{"id":2,"url":"http://www.adictosaltrabajo.com"}]},{"id":2,"content":"content_2","created":1285326000000,"title":"titulo_2"},{"id":3,"content":"content_3","created":1285327200000,"title":"titulo_3","links":[{"id":3,"url":"http://www.autentia.com"}]}]
/v1/api/notes?fields=id,title GET Los campos id y title de todas las notas del usuario logado (respuesta parcial).
[{"id":1,"title":"titulo_1"},{"id":2,"title":"titulo_2"},{"id":3,"title":"titulo_3"}]
/v1/api/notes?q=title:titulo_1 GET Todas las notas del usuario logado cuyo valor para el campo "title" sea "titulo_1" (consulta o filtro).
[{"id":1,"content":"content_1","created":1285322400000,"title":"titulo_1","links":[{"id":1,"url":"http://www.carlos-garcia.es"},{"id":2,"url":"http://www.adictosaltrabajo.com"}]}]
/v1/api/notes/1 GET Recupera la nota 1
{"id":1,"content":"content_1","created":1285322400000,"title":"titulo_1","links":[{"id":1,"url":"http://www.carlos-garcia.es"},{"id":2,"url":"http://www.adictosaltrabajo.com"}]}
/v1/api/notes/1/links GET Recupera los links de la nota con id 1 (asociativa)
[{"id":1,"url":"http://www.carlos-garcia.es"},{"id":2,"url":"http://www.adictosaltrabajo.com"}]
/v1/api/notes/ POST Crea una nota.

En la petición debemos enviar la cabecera HTTP Content-Type: application/json y en el playload de la petición:

{ "title": "mobiletest", "content": "Un complemento educativo para profesores y alumnos", "links": [ { "url": "http://www.mobiletest.es" }, { "url": "http://www.carlos-garcia.es" }, { "url": "http://www.autentia.com" } ] }
HTTP 200 OK
/v1/api/notes/5 DELETE Elimina la nota con identificador 5 HTTP 200 OK
/v1/api/notes/1 PUT Actualiza el contenido de la nota con identificador 1

En la petición debemos enviar la cabecera HTTP Content-Type: application/json y en el playload de la petición:

{ "title": "title_1 actualizado", "content": "content_1 actualizado", }
HTTP 200 OK

Estructura de la base de datos a los que accederá el API REST:

Al ejecutar el proyecto, Liquibase creará la base de datos si fuese necesario con el siguiente esquema (definidos en el archivo /src/main/resources/liquibase/changeLog_1_0.xml):

Aunque no esté familiarizado con Liquibase, creo que observando el archivo es suficiente como para ver las tablas y sus relaciones.

/src/main/resources/liquibase/changeLog_1_0.xml

Datos de prueba a los que consultará el API REST

Liquibase introducirá los siguientes datos de prueba definidos en el siguiente archivo /src/main/resources/liquibase/changeLog_1_1-test.xml):

/src/main/resources/liquibase/changeLog_1_1-test.xml

Estructura del proyecto:

Cómo se puede ver en la siguiente captura de pantalla, el proyecto tiene bastante contenido. Para hacer más legible el tutorial nos centraremos en los que directamente afectan a la temática (REST y OAuth2), dejándo a un lado detalles como Liquibase, JPA, Mockito, etc.

Creo que las clases y la configuración del proyecto están bien estructuradas semáticamente por paquetes (modelo, persistencia, servicios, oauth2, tests, etc.).

Captura de pantalla de la estructura del proyecto SmallNotes (haz clic para agrandar la imagen)

Captura de pantalla de la estructura del proyecto SmallNotes.

Las clases más importantes

es.carlosgarcia.smallnotes.service.SmallNotesServiceImpl

Esta clase proporciona la funcionalidad necesaria para cumplir la funcionalidad que expone el API REST.

es.carlosgarcia.smallnotes.web.oauth2.api.SmallNotesAPI

Controller de Spring MVC que implementa el API REST securizado por OAuth2

Observe que:

  • Los endpoints están protegidos con @PreAuthorize haciendo uso de las funciones definidas en el expresion handler oauthWebExpressionHandler
    (observe el archivo /src/main/resources/security/smallNotes-security-rules.xml)

  • La expresión de @PreAuthorize indica que debe ser un accessToken perteneciente a un usuario authenticado y que además tenga un escope y un role especifico.
    Con "usuario authenticado" me refiero a que hay un baile OAuth2 de nombre Client Credentials Flow para acceder a recursos protegidos por OAuth2 desde aplicaciones sin intervención del usuario.
  • Observer la sección Exception Handlers en donde mapeamos excepciones de negocio a instancias de RestApiError para notificar al usuario del problema.
    En la sección referencias dispone de un enlace a otra forma de realizar esta tarea así como documentación útil.

es.carlosgarcia.smallnotes.web.oauth2.api.SmallNotesApiHelper

Esta clase tiene métodos de utilidad relacionados con el procesado de parámetros de filtro y respuesta parcial.

es.carlosgarcia.smallnotes.web.oauth2.api.RestApiError

Representa el error de negocio que será enviado (en JSON) al cliente cuando sea necesario.

es.carlosgarcia.smallnotes.web.oauth2.api.SmallNotesApiTest

Tests unitarios de la clase es.carlosgarcia.smallnotes.web.oauth2.api.SmallNotesApi.

Archivos de configuración

/src/main/resources/security/security.xml

Configuración normal de los beans necesarios para una seguridad clásica por formulario de usuario/contraseña:

/src/main/resources/security/smallNotes-security-rules.xml

Definición de la protección de endpoints, en donde podemos observar:

  • En la url /oauth/token es donde en el baile oauth2, un usuario ya authenticado (en la url /oauth/authorize) obtiene un accessToken para el token de autorización que obtuvo en la primera parte del baile de oauth2.

    Los parámetros que debe recibir son los especificados en el estándar de OAuth2 (code, el hash de client_id+client_secret, state y redirect_uri), estos serán procesados en el filtro clientCredentialsTokenEndpointFilter y validados en clientAuthenticationManager

  • La url /v1/api/notes/** define la protección del Api REST por OAuth2. Será el filtro resourceServerFilter el que extraiga el accessToken de BD (haciendo uso de TokenStore) e introducciendo el OAuth2Authentication en el contexto de seguridad de SpringSecurity SecurityContextHolder.getContext().setAuthentication(authentication);

/src/main/resources/security/smallNotes-security-oauth2.xml

En el siguiente archivo destacaremos:

  • tokenStore: Es la clase que se encargará de acceder a las tablas de base de datos relacionadas con los accessTokens.
  • clientDetailsService: Es la clase que se encargará de acceder a las tablas de base de datos relaconadas con las aplicaciones clientes que solicitan autorización, es decir, esta clase se usa principalmente en el baile OAuth2 para conseguir el accessToken).
  • userApprovalHandler: Esta clase se encarga de ver si una determinada petición ya estaba aprobada por el usuario.
  • accessConfirmationController: Esta clase se encarga de mostrar el gui de solicitud de autorización así como de recibir la respuesta del usuario en ese gui.
  • oauth:authorization-server: Este tag se engarga de crear y configurar los Beans y Filtros necesarios tanto para el proceso de authorización como para el endpoint de entrega de accessToken una vez que el usuario ha autorizado a la aplicación. Más concretamente este tag crea dos Beans principales:
    • AuthorizationEndPoint: Se encarga del proceso de autorización OAuth2 generando el token de autorización y redirigiendo al usuario al endPoint dónde escucha TokenEndPoint.
    • TokenEndPoint: Se encarga de generar un accessToken (y persistirlo) para un código de autorización que recibe como parámetro.
  • resourceServerFilter: Esta clase se encarga de registrar un filtro que extraerá el accessToken de la cabecera HTTP Authorization (o el parametro accessToken) consultará en BD ese accessToken y si existe construirá el OAut2Authentication y lo meterá en el contexto de SpringSecurity.

/src/main/resources/liquibase/changeLog_OAuth2.xml

Es un archivo de creación de las tablas relacionadas con OAuth2 en formato Liquibase, el cuál se he creado extrayendo dicha definición del código fuente de Spring Security OAuth2.

Un par de observaciones a destacar:

  1. El campo authentication de la tabla oauth_access_token es una serialización de la instancia OAuth2Authentication asociada a ese accessToken, es decir a los permisos exactos que concedió el usuario en el momento de crear el token.
    Posteriormente, un filtro de Spring Security OAuth2 se encargará de consultar esta tabla para ver si el token recibido en la Cabecera HTTP Authorization (o parámetro accessToken) existe e asociarlo al contexto de Spring Security.
  2. La tabla oauth_client_details contiene las aplicaciones autorizadas para obtener accessTokens. (mediante el baile OAuth).
  3. El campo authorized_grant_types de la tabla oauth_client_details indica que clases de bailes permitimos para ese cliente, implicit, authorization_code, etc. (es un tema extenso, ver la documentación o preguntarme).
  4. El campo scope de la tabla oauth_client_details nos sirve para securizar los recursos de forma lógica.

    Por ejemplo scopes de Facebook y los scopes de Google

Descargar código fuente

A continuación puede descargarse los proyectos.

Ambos proyectos son projectos maven, por lo que puede por ejemplo iniciarlos mediante el comando:

  • mvn tomcat:run -Dmaven.tomcat.port=8080 (smallNotes)
  • mvn tomcat:run -Dmaven.tomcat.port=9080 (smallNotesExternalWebApp)

Para smallNotes, debes establecer las propiedades de conexión a la bd (que será creada automáticamente) en el archivo appcore.properties

Referencias y enlaces interesantes:

Conclusiones

A fecha de hoy, 25-Julio-2012, el proyecto Spring Security OAuth2, está a punto de ser una release (continua en milestone). Desde mi punto de vista, aunque el código fuente es de gran calidad y viene con un buen ejemplo, he echado mucho en falta más documentación para poder comprender como encajan las piezas, así que en muchos casos he tenido que ir analizando el código fuente.

Supongo que será cuestión de tiempo que la comunidad vaya nutriendo este estupendo proyecto con más documentación, tutoriales y ejemplos.

Es una implementación madura de la especificación OAuth2 (y OAuth1), además es muy completa pues se cubren todos los flujos (grantTypes) para la obtención de accessToken para cada tipo de aplicación (JavaScript, móviles, servidor, dispositivo tipo televisión, ...)

Espero que os haya sido de utilidad.

Un saludo.
Carlos García.

A continuación puedes evaluarlo:

Regístrate para evaluarlo

Por favor, vota +1 o compártelo si te pareció interesante

Share |
Anímate y coméntanos lo que pienses sobre este TUTORIAL:

Fecha publicación: 2014-07-24-12:11:59

Autor: vimicasa

Excelente tutorial!!

Esto mismo funcionaria para la version 3.0.5 de Spring??

Fecha publicación: 2013-01-03-17:06:23

Autor: RafaGloin

Buenas Carlos,

Ya tengo implementada mi api REST con Oauth2, pero me gustaría adaptar tu ejemplo para usar el grant_types de tipo password , quiero que los clientes móviles obtengan el token directamente.

He añadido a la definición oauth:authorization-server el campo oauth:password pero no funciona me dice que el SecurityContext esta vacío.

Para el cliente estoy usando Spring for Android, agradecería tu ayuda.

Saludos

Fecha publicación: 2013-01-03-16:50:05

Autor: RafaGloin

Buenas Carlos,

Ya tengo implementada mi api REST con Oauth2, pero me gustaría adaptar tu ejemplo para usar el grant_types de tipo password , quiero que los clientes móviles obtengan el token directamente.

He añadido a la definición oauth:authorization-server el campo oauth:password pero no funciona me dice que el SecurityContext esta vacío.

Para el cliente estoy usando Spring for Android, agradecería tu ayuda.

Saludos

Fecha publicación: 2012-12-19-11:57:42

Autor: RafaGloin

Gracias Carlos, has sido de gran ayuda.

Estoy de acuerdo contigo en la falta de documentación sobre la tecnología, y la que hay es muy pobre.
Compraré el libro de O'Relly para pulir conocimientos.

Saludos

Muchas gracias

Fecha publicación: 2012-12-18-14:08:29

Autor: carlosgp

Gracias Rafa.

Veo que te has quedado a la mitad del baile OAuth, es decir, en el proceso de autenticación del token aleatorio por parte del usuario en el servicio al que pretendes acceder (En este caso SmallNotes).

El motivo es que la URL de autorización debe ser una del la aplicación Proveedora del servicio (SmallNotes), es decir la que tiene el recurso a proteger y cuyo acceso debe ser autorizado por el usuario (El usuario debe autorizar el acceso a SmallNotesExternalWebApp)

Es decir, la url no puede ser localhost:8080/web_service/?code=jnGZuJ, sino que tiene que ser una http://localhost:8080/smallNotes/ ...

Escribela igual que en las capturas de pantalla.

Saludos

Fecha publicación: 2012-12-18-09:11:59

Autor: RafaGloin

Buenas Carlos,

Primero felicitarte por este gran tutorial, es lo mejor que he encontrado. Pero me encuentro con un problema.

Estoy probando con el pluging RestClient pero cuando llego a la captura que tu numeras como C y acepto el acceso, el sistema me devuelve un código que no es el token, me lo devuelve a la dirección que indico como endpoint de re dirección (Algo como así localhost:8080/web_service/?code=jnGZuJ). Mi pregunta es, ¿cual sería el siguiente paso para obtener el token a partir del código?

Muchas gracias por tu ayuda.
Saludos

Fecha publicación: 2012-08-07-15:48:38

Autor: Wanderlei

Check that you add the header "Authorization: Bearer <your_access_token>"
instead of "Authorization: OAuth <your_access_token>"

Yep, looks that is the problem. My Restclient use Authorization: OAuth <token> instead of Authorization: Bearer <your_access_token>. But even adding manually a new authorization header with content Bearer <token> does not work. =( And also "Authorization: OAuth <your_access_token>" should not be supported? Thank you again.

Fecha publicación: 2012-08-07-13:42:52

Autor: carlosgp

Hi Wanderlei.

Check that you requested the needed scope (read,write) the your requests. (First Image of OAuth2 Dance capture screens)

Fecha publicación: 2012-08-07-12:37:12

Autor: carlosgp

Hi Wanderlei.

Check that you add the header "Authorization: Bearer <your_access_token>"
instead of "Authorization: OAuth <your_access_token>"

Be sure that accesstoken is not expired too.

Regards

Fecha publicación: 2012-08-07-01:24:52

Autor: Wanderlei

I changed the properties in smallNotes to (I don't want to install mysql to run the example):

app.environment=developer
db.driverClassName=org.h2.Driver
db.url=jdbc:h2:mem:smallNotesDB;DB_CLOSE_DELAY=-1
db.username=sa
db.password=

with my browser pointing to http://localhost:8080/smallNotes/ everything is fine. I can go on an log with user1@smallnotes.es / 1234

But when I try to access using a rest call (with firefox rest client) I receive the error:

{"message":"Access is denied","infoUrl":"http://yourAppUrlToDocumentedApiCodes.com/api/support/3","httpStatusCode":"BAD_REQUEST","apiCode":"GENERIC","developerMessage":"Access is denied"}


ps: I can see the access token gerenation + the confirmation screen. The problem happens when I call http://localhost:8080/smallNotes/v1/api/notes/1 on firefox restclient. Any Idea?


Fecha publicación: 2012-07-31-13:33:06

Autor: carlosgp

Gracias Miguel,

Sí, muchísimos ratos libres para ir dándole forma.

Un saludo ;-)

Fecha publicación: 2012-07-30-22:47:50

Autor: marlandy

Excelente tutorial Carlos!!! Se nota que te lo has currado... :)