Servicio Rest con Spring Security

Servicio Rest con Spring Security, Oauth y Gradle

La seguridad en una aplicación o servicio Web es muy importante, el dr. Jules White da una buena introducción de 4 minutos a Oauth 2.0, un protocolo de delegación o especificación que es útil para transmitir decisiones de autorización a través de servicios Web y APIs.

Oauth es usado por grandes empresas como Google y Facebook entre otras, también las apps que dicen ser muy seguras deberían utilizarlo, debido a un factor, las cookies son malas!.

Cuando un usuario inicia una sesión en una aplicación web, el servidor de aplicaciones establece un valor a una cookie que es recogido por el navegador del usuario. El navegador incluye el mismo valor de la cookie en cada petición enviada a la misma máquina hasta que la cookie expire. Cuando el servidor de aplicaciones recibe una petición se puede comprobar si las cookies que se le atribuyen contienenen un valor que identifica a un usuario específico. Si existe el valor en la cookie, a continuación, el servidor puede considerar la solicitud para ser autenticada. Esto da lugar a ciertos ataques como man-in-the-middle (MITM), cross-site request forgery (CSRF) y cross-site scripting (XSS). Oauth se ha diseñado para evitar esta clase de problemas.

Un diagrama demostrativo de lo que se quiere lograr a futuro desde un cliente Android es la siguiente:

Servicio Rest con Spring Security

Para comprender un poco todo esto, no vamos a rehacer la rueda, por lo tanto se explicará y se probará un buen ejemplo de github el cuál es un Servicio Rest con Spring Security y Oauth. Antes debemos descargar e instalar Spring Tool Suite (STS), una suite para programar enfocada en Spring Framework para programar basada en Eclipse (En este tutorial la versión de Eclipse que usa STS es Eclipse Mars 4.5.1).

Vamos a integrar Gradle al STS, para esto vamos al menu Help -> Eclipse Market Place y descargamos Gradle IDE Pack.

gradleidepack

Descargamos el ejemplo que mencionamos antes con GIT desde la terminal (o si no lo tienes, puedes descargar el .zip).

$ git clone https://github.com/royclarkson/spring-rest-service-oauth.git

Abrimos el ejemplo con el STS IDE: File -> Import -> Gradle Project. Seleccionamos la carpeta del proyecto, seleccionamos Build Model y Finish.

gradle1

Ahora en el Package Explorer del STS, el proyecto se despliega como sigue:

gradle2

Por defecto el proyecto está para correr por el protocolo HTTP, entonces vamos a hacer que corra por HTTPS el cuál agrega seguridad mediante SSL. Ya que usar Oauth sin conexión SSL no sirve de nada.

Para esto abrimos el archivo application.properties lo modificamos como sigue:

spring.profiles.active=https

El ejemplo viene con su propio almacenamiento de llaves jks para usar SSL, por supuesto esto no se debe hacer en un ambiente productivo, nosotros debemos crear nuestro propio almacenamiento de llaves.

Para ejecutar el ejemplo, seleccionamos el proyecto con click derecho, a continuación vamos a Run as -> Spring Boot App.

Comprobamos que todo vaya bien al acceder mediante el navegador a la URL https://localhost:8443/greeting:

oauth ssl

Al haberse ejecutado, se crea una base en memoria de tipo hsqldb gracias a:

  • En el archivo build.gradle se encontraba la siguiente declaración para agregar las dependencias necesarias
 compile("org.hsqldb:hsqldb") 
  • En el paquete hello.data se definen las entidades de la base de datos (Role.java y User.java) y el “repositorio” (UserRepository.java) en donde podemos implementar métodos CRUD (Create, Read, Update and Delete) a la base de datos.
  • Con el archivo import.sql se hacen inserts a las tablas creadas por el repositorio.
insert into user(id, name, login, password) values (1,'Roy','roy','spring');
insert into user(id, name, login, password) values (2,'Craig','craig','spring');
insert into user(id, name, login, password) values (3,'Greg','greg','spring');
 
insert into role(id, name) values (1,'ROLE_USER');
insert into role(id, name) values (2,'ROLE_ADMIN');
insert into role(id, name) values (3,'ROLE_GUEST'); 

insert into user_role(user_id, role_id) values (1,1);
insert into user_role(user_id, role_id) values (1,2);
insert into user_role(user_id, role_id) values (2,1);
insert into user_role(user_id, role_id) values (3,1);

Ejecutamos desde el STS las pruebas unitarias, para esto vamos a el paquete en /src/test/java/hello:

oauth2

Seleccionamos con click derecho GreetingControllerTest.java la cual es la clase del cliente que nos va a servir para consumir el servicio rest que corre actualmente. Vamos a Run As -> JUnit Test. En la ventana de JUnit del STS se puede observar que todo se ha ejecutado correctamente:

junit1

Veamos como funciona todo desde la terminal utilizando el comando curl:

  • El siguiente código nos va a dar problemas debido a que se necesita permitir la conexión SSL con un certificado válido.
$ curl https://localhost:8443/greeting
curl: (60) SSL certificate problem: self signed certificate
More details here: http://curl.haxx.se/docs/sslcerts.html
...
  • Agregamos la opción -k para permitir la conexión SSL sin un certificado válido, aunque de igual forma se va a bloquear el acceso debido a que no accedemos con un “Bearer token” válido. Los “Bearer tokens” son el tipo predeterminado de tokens de acceso. Ellos se activan automáticamente cuando un servidor de autorización o de recursos del servidor se inicializan.

$ curl -k https://localhost:8443/greeting

{"error":"unauthorized","error_description":"Full authentication is required to access this resource"}

  • El siguiente ejemplo vamos a solicitar acceso al servicio, al solicitarlo nos va a devolver ciertos tokens;“access_token” el cuál lo vamos a usar como un “tipo bearer”  el cuál tiene un periodo de vida en segundos señalado en el parámetro “expires_in”.“refresh_token” se va a utilizar una vez caducado el “token de tipo bearer” para volver a pedir un nuevo token de acceso.
$ curl -X POST -k -vu clientapp:123456 https://localhost:8443/oauth/token -H "Accept: application/json" -d "password=spring&username=roy&grant_type=password&scope=read%20write&client_secret=123456&client_id=clientapp"
...
{"access_token":"1075d56b-e5bb-4060-882b-d7096ffe5e75",
"token_type":"bearer",
"refresh_token":"c08387f2-a755-4656-8484-3e19c2703980",
"expires_in":42127,
"scope":"read write"}
  • Con el token de tipo Bearer, lo utilizamos para acceder al recurso:
$ curl -k https://localhost:8443/greeting -H "Authorization: Bearer 1075d56b-e5bb-4060-882b-d7096ffe5e75"{"id":1,"content":"Hello, Roy!"}
  • Al caducar el token de acceso podemos pedir otro utilizando el “refresh_token”. Cabe mencionar que en la clase GreetingControllerTest.java de pruebas unitarias, el método getAccessToken() hace todo esto automáticamente.
$ curl -X POST -k -vu clientapp:123456 https://localhost:8443/oauth/token -H "Accept: application/json" -d "grant_type=refresh_token&refresh_token=c08387f2-a755-4656-8484-3e19c2703980&client_secret=123456&client_id=clientapp"
...
{"access_token":"2b6e1004-c5b7-4953-b126-3c25564074ee","token_type":"bearer","refresh_token":"c08387f2-a755-4656-8484-3e19c2703980","expires_in":43199,"scope":"read write"}
  • Al observar el archivo import.sql nos podemos dar cuenta que el usuario “Roy” tiene dos roles; el rol “ROLE_USER” y “ROLE_ADMIN” en la base de datos. Al ver la clase OAuth2ServerConfiguration.java, en el método de ResourceServerConfiguration, se especifica que solamente los usuarios con el rol ADMIN (“ROLE_ se inserta automáticamente”) van a poder acceder a la URL “/users” cuyo controlador es la clase UserController.java. Los demás solamente a “/greeting” , esto mientras estén autenticados correctamente:

//...
@Configuration
public class OAuth2ServerConfiguration {
//...
@Override
public void configure(HttpSecurity http) throws Exception {
// @formatter:off
http
.authorizeRequests()
.antMatchers("/users").hasRole("ADMIN")
.antMatchers("/greeting").authenticated();
// @formatter:on
}

  • Por lo anterior, con el usuario “Roy” podemos ver lo que contiene la tabla “Users” gracias al método  getUsers() del controlador UserController.java. Para ver el contenido, desde la terminal ejecutamos:
$ curl -k https://localhost:8443/users -H "Authorization: Bearer 2b6e1004-c5b7-4953-b126-3c25564074ee"[{"id":1,"name":"Roy","login":"roy","password":"spring"},{"id":2,"name":"Craig","login":"craig","password":"spring"},{"id":3,"name":"Greg","login":"greg","password":"spring"}]
  • Suponiendo que ya tenemos un token de acceso para el usuario “Craig” podemos comprobar que no tiene acceso a la URL “/users”:
$ curl -k https://localhost:8443/users -H "Authorization: Bearer 86b1a1b6-b9d7-447b-9803-e5fdb5592ca7"
{"error":"access_denied","error_description":"Access is denied"}
  • Aunque “Craig” si puede acceder a “/greeting”:
$ curl -k https://localhost:8443/greeting -H "Authorization: Bearer 86b1a1b6-b9d7-447b-9803-e5fdb5592ca7"
{"id":3,"content":"Hello, Craig!"}

El objetivo de todo esto ha sido mostrar de manera rápida e introductoria el como crear un buen servicio REST con Spring, así como explicar lo que es Oauth.

Si buscas conectar MySQL o ver otra manera de crear servicios REST con Spring y Oauth, dale un vistazo a este código el cuál es un servicio REST para una APP que he creado el cuál guarda imágenes subidas desde un cliente Android, las modifica y las envía de regreso al cliente en una red cifrada.

Esta entrada está también disponible en us.

Deja un comentario

Tu dirección de correo electrónico no será publicada. Los campos necesarios están marcados *