Autenticación con OAuth y Keycloak en un servicio REST con JAX-RS y Spring Boot

Escrito por el , actualizado el .
blog-stack java planeta-codigo programacion
Comentarios

Keycloak es un proveedor de OAuth que podemos usar en nuestras aplicaciones y servicios para proporcionar autenticación, autorización, SSO y también añadir seguridad a los servicios REST que desarrollemos como muestro en este artículo. OAuth tiene varias ventajas sobre usar autenticación Basic.

Keycloak
Java

Una forma de autenticar a los clientes de un servicio REST es usar autenticación Basic que se basa en añadir una cabecera en la petición en la que se incluye un usuario y contraseña. La autenticación Basic es sencilla pero para que sea segura ha de usar el protocolo seguro HTTPS.

Sin embargo, presenta otros inconvenientes y es que si al servicio van a acceder varios clientes y a uno queremos impedirle el acceso no podremos hacerlo sin cambiar el usuario y contraseña lo que obligará al resto de clientes actualizarse para usar las nuevas credenciales si las comparten, que no siempre es posible sobre todo si esos clientes están fuera de nuestro control. Para solventar el segundo problema tenemos la posibilidad de segurizar el servicio REST con el protocolo OAuth.

Teniendo un servicio web REST implementado con JAX-RS y Spring Boot añadirle seguridad con OAuth mediante el proveedor Keycloak es lo que muestro en este artículo. En el servicio REST bastará que usemos el adaptador para Spring Boot de Keycloak y añadamos en Keycloak cierta configuración que consistirá en un realm y el registro de un cliente. Para acceder al servicio REST usaremos el flujo client_credentials que nos permitirá obtener un token usando las credenciales del cliente.

Iniciado Keycloak con Docker y Docker Compose accedemos al panel de administración con el navegador, en mi caso en http://localhost:9080 con el usuario admin y contraseña admin según lo indicado en el archivo docker-compose.yml.

1
2
3
4
5
6
7
keycloak:
    image: jboss/keycloak
    ports:
        - 9080:8080
    environment:
        - KEYCLOAK_USER=admin
        - KEYCLOAK_PASSWORD=admin
1
$ docker-compose up

Creamos un realm, en el ejemplo llamado springbootjaxrs y un cliente con id client, además crearemos un rol api y se lo asignaremos al cliente.

Una vez realizada la configuración en el servidor de OAuth/Keycloak obtendremos la configuración para el adaptador de Keycloak para el servicio REST desde la pestaña Installation que añadiremos en el fichero de configuración de Spring Boot application.yml. Además, indicaremos que el cliente solo aceptará access tokens mediante la opción bearer-only de modo que no hará redirecciones para autenticar.

Indicaremos también el rol que deberá poseer el cliente para acceder al servicio REST junto que URLs del servicio estarán autenticadas por OAuth. Añadida la configuración al archivo application.yml el servicio REST es totalmente inconsciente de la autenticación que se realizará con OAuth y Keycloak.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
server:
    port: 8443
    ssl:
        key-store: classpath:keystore.jks
        key-store-password: secret
        key-password: secret

keycloak:
  realm: springbootjaxrs
  realmKey: MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAkTsyao42k7UyRE5LLlHXUaA7yM5MIMdQYqhaypZCOC5tOX8c+QlbBVzyhS55sDqmar5pelKyHT/6Zpr40GtxCnOqTBJ1pI28QXdOnPC8OVXJSNttuyeb2b3PK6xstm/jCpi1Dduw7fVmQDBKUXhmQwTdoOGQe8F3O0OaL2ht/H3Tu+LcvobWGtfN2vj1LACfUCO4qom9soWS755Km7KqE2xNnOPaEZ+IxDfTNJu7VHekak+OjbjM3EZdhOWkhw6M3+pnQ+9FKgHczPfYHiIG3wRlQm2ly7FQUnW1LKnvrMsjdaTlebCDnSmCcn6ByT+vC53URbGdb8a0+UAh+66kowIDAQAB
  bearer-only: true
  auth-server-url: http://localhost:9080/auth
  ssl-required: external
  resource: client
  use-resource-role-mappings: true
  securityConstraints[0]:
      securityCollections[0]:
          name: api
          authRoles:
              - api
          patterns:
              - /api/*
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
package io.github.picodotdev.blogbitix.springbootjaxrsoauth;

import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Component;

import javax.ws.rs.GET;
import javax.ws.rs.Path;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

@Component
@Path("/message")
public class MessageResource {

    @Autowired
    private MessageService messageService;

    @GET
    @Produces("application/json")
    public Message message(@QueryParam("message") String message) {
        return messageService.create(message);
    }

}

Iniciado Keycloak y el servicio REST con el comando gradlew run podemos iniciar el proceso de obtención de un access token y llamar al servicio proporcionando el access token obtenido y ver que pasa si no proporcionamos token o uno modificado o inválido. Para obtener el access token podemos emplear curl accediendo al endpoint de Keycloak para obtenerlos.

1
2
$ curl -i http://localhost:9080/auth/realms/springbootjaxrs/.well-known/openid-configuration
$ curl -i http://localhost:9080/auth/realms/springbootjaxrs/protocol/openid-connect/token -d "grant_type=client_credentials&client_id=client&client_secret=751e83fe-ee29-4239-81f8-ceafab189c66"

Obtenido el access token si no lo proporcionamos en la llamada al servicio REST observaremos que la respuesta que obtenemos es un código de estado HTTP 401 indicando que se necesitan proporcionar las credenciales que con OAuth es un token.

1
$ curl -ik https://localhost:8443/api/message?message=Hola

Proporcionando el token mediante una cabecera de la petición el servicio nos devolverá los datos que proporciona. Si el token no es válido obtendremos un error HTTP 401.

1
$ curl -ik -H "Authorization: Bearer eyJhbGciOiJSUzI1NiIsInR5cCIgOiAiSldUIiwia2lkIiA6ICJteEh2a2FwRC1xSXRUVEhqZXZnRDdxYXVKeXU5Ry01RFp4VVpqMjJlZVc0In0.eyJqdGkiOiJiMWY0OTA5YS00N2JkLTRkNGMtOGVlNS0yMTE1ZmMxODliMGQiLCJleHAiOjE0NzQ3MDg4ODMsIm5iZiI6MCwiaWF0IjoxNDc0NzA4NTgzLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjkwODAvYXV0aC9yZWFsbXMvc3ByaW5nYm9vdGpheHJzIiwiYXVkIjoiY2xpZW50Iiwic3ViIjoiNzcyZWU4MDktMDJjOC00NGEyLTllMTctYTUxMzllYTkwM2Y5IiwidHlwIjoiQmVhcmVyIiwiYXpwIjoiY2xpZW50IiwiYXV0aF90aW1lIjowLCJzZXNzaW9uX3N0YXRlIjoiNzY2MzY2OTktZTQzOS00ZjE4LTgxMzMtODFjYTU4NjNhZjg0IiwiYWNyIjoiMSIsImNsaWVudF9zZXNzaW9uIjoiYjUwYTRjOWQtMDI1Yy00ZTU1LWFjOWYtYmUxMzE3YTY5ZGUxIiwiYWxsb3dlZC1vcmlnaW5zIjpbXSwicmVhbG1fYWNjZXNzIjp7InJvbGVzIjpbImFwaSJdfSwicmVzb3VyY2VfYWNjZXNzIjp7ImNsaWVudCI6eyJyb2xlcyI6WyJhcGkiXX0sImFjY291bnQiOnsicm9sZXMiOlsibWFuYWdlLWFjY291bnQiLCJ2aWV3LXByb2ZpbGUiXX19LCJjbGllbnRIb3N0IjoiMTcyLjE3LjAuMSIsImNsaWVudElkIjoiY2xpZW50IiwibmFtZSI6IiIsInByZWZlcnJlZF91c2VybmFtZSI6InNlcnZpY2UtYWNjb3VudC1zcHJpbmdib290amF4cnMiLCJjbGllbnRBZGRyZXNzIjoiMTcyLjE3LjAuMSIsImVtYWlsIjoic2VydmljZS1hY2NvdW50LXNwcmluZ2Jvb3RqYXhyc0BwbGFjZWhvbGRlci5vcmcifQ.QvS5azEf5DgHon-Nd5ghhB95CR7z7aVxz611gfWOfEOMxhHlVvahcyCYzHpott1AOj7h9tbjNJe1abUX_BlVlbQ7jvQg_zp1h67dHl5u1kdqxUZlWaucp0bWaDlmfHQV-JJOOp7lpfWcM2H63A0fu6uMVIrumKlwaprw_R5_wTxmEuNdekEVCRaPh2X2s3c92AGOWtqvfDed5nW6DECH6mt1I4nA56RQPs0FAb6DTh-oYR-ZHFsDTprrUu-HZ5R9bl8459zyqFDl08e9qe8HgdSd5vAiHsfmzMd2T6ZqCRHI391rIs-at947OKQH82r4wvSYCwlTB75m2Lhzidto_A" https://localhost:8443/api/message?message=Hola

Para usar Keycloak en una aplicación Java con Spring Boot deberemos añadir algunas dependencias al proyecto que usando Gradle como herramienta de construcción serían las siguientes.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
...

dependencies {
    compile('org.springframework.boot:spring-boot-starter-web')
    compile('org.springframework.boot:spring-boot-starter-jersey')

    compile('org.keycloak:keycloak-spring-boot-adapter:2.2.0.Final')
    compile('org.keycloak:keycloak-tomcat8-adapter:2.2.0.Final')
    compile('org.keycloak:keycloak-core:2.2.0.Final')

    compile('org.apache.httpcomponents:httpcomponents-client:4.5.2')
    compile('javax.json:javax.json-api:1.0')
    compile('org.glassfish:javax.json:1.0.4')

    testCompile('org.springframework.boot:spring-boot-starter-test')
}

...

Un buen libro sobre OAuth que he leído es Mastering OAuth 2.0 que explica detalladamente el protocolo OAuth junto con el resto de formas de obtener un token además del mostrado en este artículo usando las credenciales del cliente.

En el siguiente artículo mostraré un cliente del servicio REST autenticado con OAuth en Java que haga las mismas llamadas que con curl pero usando código Java mediante la librería HttpComponents.

El código fuente completo del ejemplo puedes descargarlo del repositorio de ejemplos de Blog Bitix alojado en GitHub y probarlo en tu equipo ejecutando el comando ./gradle run.