Appearance
Keycloak
Keycloak es una solución de código abierto para gestión de identidad y acceso (IAM - Identity and Access Management) de grado empresarial. Esta basada en la máquina virtual de Java (JVM). Proporciona autenticación, autorización, Single Sign-On (SSO), y gestión de usuarios para aplicaciones y servicios. Como ventajas, esta herramienta nos ofrece lo siguiente:
- Single Sign-On (SSO): Una sola autenticación para múltiples aplicaciones
- Protocolos estándar: OAuth 2.0, OpenID Connect, SAML 2.0
- Federación de identidad: Integración con proveedores externos
- Gestión centralizada: Usuarios, roles y permisos en un solo lugar
- Personalización: Temas, flujos de autenticación, extensiones
- Open Source: Gratuito y con comunidad activa
Esta es una guía esencial para introducirse en la gestión de identidad con Keycloak
Conceptos clave
Para introducirnos en Keycloak es necesario comprender los siguientes conceptos:
- Realm: Traduce reino (o gobierno) y se refiere a un espacio aislado que gestiona usuarios, roles y aplicaciones
- Client: Aplicación que solicita autenticación a Keycloak
- User: Usuario que se autentica en el sistema
- Role: Permisos asignados a usuarios o grupos
- Identity Provider: Proveedor externo de identidad (Google, GitHub, LDAP)
- Token: JWT que contiene información de autenticación y autorización
Instalación de Keycloak
Docker (modo desarrollo)
bash
docker run -d \
--name keycloak \
-p 8080:8080 \
-e KEYCLOAK_ADMIN=admin \
-e KEYCLOAK_ADMIN_PASSWORD=admin \
quay.io/keycloak/keycloak:latest \
start-dev
# Acceder a la consola
open http://localhost:8080Instalación local
bash
# Descargar Keycloak
wget https://github.com/keycloak/keycloak/releases/download/23.0.0/keycloak-23.0.0.zip
unzip keycloak-23.0.0.zip
cd keycloak-23.0.0
# Iniciar en modo desarrollo
bin/kc.sh start-devConfiguración inicial
Crear Realm y Client
Lo primero es definir el realm siguiendo los siguientes pasos:
- Acceder a
http://localhost:8080 - Login con
admin/admin - Crear nuevo Realm: my-realm
- Crear Client:
- Client ID: my-app
- Client Protocol: openid-connect
- Access Type: confidential
- Valid Redirect URIs:
http://localhost:3000/*
- Obtener Client Secret de la pestaña Credentials
Crear usuario de prueba
Debe crearse un primer usuario para gestionar algún acceso. Para ello considera lo siguiente:
- En el Realm my-realm, ir a Users
- Crear usuario: testuser
- Establecer contraseña en Credentials
- Asignar roles si es necesario
Ejemplo esencial: Obtener token con JBang
java
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.httpcomponents.client5:httpclient5:5.2.1
//DEPS com.fasterxml.jackson.core:jackson-databind:2.15.2
//JAVA 21
import org.apache.hc.client5.http.classic.methods.HttpPost;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.StringEntity;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
import java.util.HashMap;
import java.util.Map;
/**
* Keycloak Token Example
* Obtains access token using password grant
*/
public class KeycloakTokenExample {
public static void main(String[] args) throws Exception {
System.out.println("Keycloak Token Example");
System.out.println("=====================\n");
// Configuración
String keycloakUrl = "http://localhost:8080";
String realm = "my-realm";
String clientId = "my-app";
String clientSecret = "your-client-secret"; // Obtener de Keycloak
String username = "testuser";
String password = "testpass";
// URL del token endpoint
String tokenUrl = String.format(
"%s/realms/%s/protocol/openid-connect/token",
keycloakUrl, realm
);
// Preparar request
Map<String, String> params = new HashMap<>();
params.put("grant_type", "password");
params.put("client_id", clientId);
params.put("client_secret", clientSecret);
params.put("username", username);
params.put("password", password);
String formData = params.entrySet().stream()
.map(e -> e.getKey() + "=" + e.getValue())
.reduce((a, b) -> a + "&" + b)
.orElse("");
// Hacer request
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpPost post = new HttpPost(tokenUrl);
post.setHeader("Content-Type", "application/x-www-form-urlencoded");
post.setEntity(new StringEntity(formData));
String response = client.execute(post, httpResponse ->
EntityUtils.toString(httpResponse.getEntity())
);
// Parse response
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree(response);
String accessToken = json.get("access_token").asText();
String refreshToken = json.get("refresh_token").asText();
int expiresIn = json.get("expires_in").asInt();
System.out.println("Access Token obtenido:");
System.out.println(accessToken.substring(0, 50) + "...");
System.out.println("\nExpira en: " + expiresIn + " segundos");
System.out.println("\nRefresh Token:");
System.out.println(refreshToken.substring(0, 50) + "...");
}
}
}Ejecutar:
bash
jbang KeycloakTokenExample.javaEjemplo: Validar token y obtener info de usuario
java
///usr/bin/env jbang "$0" "$@" ; exit $?
//DEPS org.apache.httpcomponents.client5:httpclient5:5.2.1
//DEPS com.fasterxml.jackson.core:jackson-databind:2.15.2
//JAVA 21
import org.apache.hc.client5.http.classic.methods.HttpGet;
import org.apache.hc.client5.http.impl.classic.CloseableHttpClient;
import org.apache.hc.client5.http.impl.classic.HttpClients;
import org.apache.hc.core5.http.io.entity.EntityUtils;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.JsonNode;
public class KeycloakUserInfoExample {
public static void main(String[] args) throws Exception {
System.out.println("Keycloak UserInfo Example");
System.out.println("=========================\n");
if (args.length == 0) {
System.err.println("Usage: jbang KeycloakUserInfoExample.java <access_token>");
System.exit(1);
}
String accessToken = args[0];
String keycloakUrl = "http://localhost:8080";
String realm = "my-realm";
// URL del userinfo endpoint
String userInfoUrl = String.format(
"%s/realms/%s/protocol/openid-connect/userinfo",
keycloakUrl, realm
);
// Hacer request
try (CloseableHttpClient client = HttpClients.createDefault()) {
HttpGet get = new HttpGet(userInfoUrl);
get.setHeader("Authorization", "Bearer " + accessToken);
String response = client.execute(get, httpResponse ->
EntityUtils.toString(httpResponse.getEntity())
);
// Parse response
ObjectMapper mapper = new ObjectMapper();
JsonNode json = mapper.readTree(response);
System.out.println("User Information:");
System.out.println(" Username: " + json.get("preferred_username").asText());
System.out.println(" Email: " + json.get("email").asText());
System.out.println(" Name: " + json.get("name").asText());
System.out.println(" Sub: " + json.get("sub").asText());
if (json.has("realm_access")) {
System.out.println("\nRoles:");
json.get("realm_access").get("roles").forEach(role ->
System.out.println(" - " + role.asText())
);
}
}
}
}Ejecutar:
bash
# Primero obtener token
TOKEN=$(jbang KeycloakTokenExample.java | grep "Access Token" -A 1 | tail -1)
# Luego validar
jbang KeycloakUserInfoExample.java "$TOKEN"Integración con Spring Boot
El archivo application.yml de Spring Boot puede contener lo siguiente:
yaml
spring:
security:
oauth2:
resourceserver:
jwt:
issuer-uri: http://localhost:8080/realms/my-realm
jwk-set-uri: http://localhost:8080/realms/my-realm/protocol/openid-connect/certs
keycloak:
realm: my-realm
auth-server-url: http://localhost:8080
resource: my-app
credentials:
secret: your-client-secretPara la clase SecurityConfig se puede aplicar un filterChain como el siguiente:
java
@Configuration
@EnableWebSecurity
public class SecurityConfig {
@Bean
public SecurityFilterChain filterChain(HttpSecurity http) throws Exception {
http
.authorizeHttpRequests(auth -> auth
.requestMatchers("/public/**").permitAll()
.anyRequest().authenticated()
)
.oauth2ResourceServer(oauth2 -> oauth2
.jwt(Customizer.withDefaults())
);
return http.build();
}
}Configuración de roles y permisos
Crear roles
Para crear roles considera lo siguiente:
- En Keycloak Admin Console, ir a Realm Roles
- Crear roles:
admin,user,manager - Asignar roles a usuarios en Users - Role Mappings
Client Scopes
Para crear scopes considera lo siguinte:
- Ir a Client Scopes
- Create scope: "custom-scope"
- Agregar mappers para incluir claims personalizados
Comandos útiles
bash
# Exportar configuración de realm
bin/kc.sh export --dir /tmp/keycloak-export --realm my-realm
# Importar configuración
bin/kc.sh import --dir /tmp/keycloak-export
# Modo producción
bin/kc.sh start --hostname=keycloak.example.com
# Ver logs
docker logs -f keycloakEndpoints destacados
A continuación, veamos los endpoints para token, userinfo, logout, openid-configuration (Well-known) y certs (JWKS):
bash
POST http://localhost:8080/realms/{realm}/protocol/openid-connect/token
GET http://localhost:8080/realms/{realm}/protocol/openid-connect/userinfo
POST http://localhost:8080/realms/{realm}/protocol/openid-connect/logout
GET http://localhost:8080/realms/{realm}/.well-known/openid-configuration
GET http://localhost:8080/realms/{realm}/protocol/openid-connect/certsNótese que debe pasarse como parámetro
realm.
Buenas prácticas para continuar con Keycloak
Para resumir, Keycloack nos ofrece...
- Usar HTTPS en producción: Nunca exponer Keycloak sin TLS
- Rotar secrets: Cambiar client secrets regularmente
- Tokens de corta duración: Configurar TTL apropiado para tokens
- Refresh tokens: Implementar renovación automática
- Roles granulares: Definir permisos específicos por funcionalidad
- Auditoría: Habilitar logs de eventos de seguridad
- Backup: Respaldar configuración de realms regularmente
Keycloak es esencial para aplicaciones empresariales que requieren autenticación y autorización robusta, proporcionando una solución completa de IAM con soporte para estándares modernos y fácil integración con aplicaciones existentes.