Qué vas a conseguir
Un Keycloak que da inicio de sesión único a las aplicaciones internas con las cuentas de Active Directory: instalado en modo producción detrás del proxy inverso, con un realm para la empresa, una aplicación por OIDC y otra por SAML, los grupos del directorio convertidos en roles, un segundo factor obligatorio y el método para el error más repetido al conectar una aplicación nueva: Invalid parameter: redirect_uri.
Antes de empezar
- Un servidor Linux con Docker (o Podman) y una base de datos PostgreSQL para Keycloak. La guía usa la versión 26.8; los menús son los de la consola de administración de la 26.
- Un nombre DNS (
sso.contoso.local) que apunte al proxy inverso (Nginx o Apache) con un certificado de la CA interna. El proxy termina TLS y habla con Keycloak por HTTP en la red interna. - Una cuenta de servicio de AD solo para lectura (
CONTOSO\svc-keycloak), con contraseña que no caduque o gestionada por el equipo de identidad, y LDAPS (636) abierto desde el servidor hacia los controladores de dominio. - NTP funcionando en el servidor de Keycloak y en los de las aplicaciones. Ver la nota del final.
1. Instalar en modo producción detrás del proxy
start arranca en modo producción: exige un hostname y no admite la base de datos de desarrollo. La configuración va en variables KC_* (cada opción --algo-asi se escribe KC_ALGO_ASI):
services: keycloak: image: quay.io/keycloak/keycloak:26.8.1 container_name: keycloak command: start environment: KC_DB: postgres KC_DB_URL: jdbc:postgresql://db01.contoso.local:5432/keycloak KC_DB_USERNAME: keycloak KC_DB_PASSWORD: ${KC_DB_PASSWORD} KC_HOSTNAME: https://sso.contoso.local KC_HTTP_ENABLED: "true" KC_PROXY_HEADERS: xforwarded KC_PROXY_TRUSTED_ADDRESSES: 10.20.0.10 KC_HEALTH_ENABLED: "true" KC_TRUSTSTORE_PATHS: /opt/keycloak/conf/truststores/contoso-root.pem KC_BOOTSTRAP_ADMIN_USERNAME: tmpadmin KC_BOOTSTRAP_ADMIN_PASSWORD: ${KC_BOOTSTRAP_ADMIN_PASSWORD} volumes: - ./contoso-root.pem:/opt/keycloak/conf/truststores/contoso-root.pem:ro ports: - "10.20.0.21:8080:8080" - "127.0.0.1:9000:9000" restart: unless-stoppedKC_PROXY_HEADERS: xforwarded: Keycloak se fía de las cabecerasX-Forwarded-*del proxy, pero solo si vienen deKC_PROXY_TRUSTED_ADDRESSES(la IP del proxy). El proxy debe enviarX-Forwarded-Proto: https.- El puerto 9000 es el de gestión (
/health,/metrics): no se publica por el proxy. - En el proxy, hacia fuera solo
/realms/,/resources/y/.well-known/./admin/y/realms/master/, solo desde la red de IT. KC_TRUSTSTORE_PATHSañade la CA interna, la que firma los certificados de LDAPS de los controladores.
docker compose up -ddocker logs keycloak 2>&1 | grep -o "Keycloak 26.8.1 on JVM"Keycloak 26.8.1 on JVMcurl -s http://127.0.0.1:9000/health/ready | grep -o '"status": "UP"' | head -1"status": "UP"El usuario tmpadmin es temporal y la consola lo avisa: entra en https://sso.contoso.local/admin/, crea en el realm master un administrador con nombre propio y OTP, y borra tmpadmin.
2. El realm de la empresa
master es solo para administrar Keycloak. Las aplicaciones van en un realm propio:
En Realm settings: pestaña Login, User registration desactivado y Forgot password desactivado (la contraseña es la de AD y se cambia por el procedimiento de siempre); pestaña Email, el servidor SMTP interno. El emisor de los tokens queda como https://sso.contoso.local/realms/contoso y todo lo que una aplicación necesita está en https://sso.contoso.local/realms/contoso/.well-known/openid-configuration.
3. Un cliente OIDC y uno SAML
La aplicación de compras (compras.contoso.local) habla OIDC:
- Client authentication activado: es una aplicación de servidor con secreto. El secreto está en la pestaña Credentials y se entrega al equipo de la aplicación por el gestor de contraseñas, junto con el Client ID y la URL de descubrimiento.
- Una aplicación de navegador pura (SPA) no puede guardar secretos: Client authentication desactivado y PKCE en la aplicación.
- Valid redirect URIs con la URI exacta. Un
*al final se admite, pero*solo abre la puerta a cualquier destino.
Para una aplicación que solo habla SAML (la wiki, por ejemplo): Clients › Import client con los metadatos del proveedor de servicio, o Create client con Client type: SAML y como Client ID su Entity ID (https://wiki.contoso.local/saml/metadata); la URL de ACS va en la pestaña Advanced (Assertion Consumer Service POST Binding URL) y el formato del NameID, en Settings. La aplicación recibe los metadatos de Keycloak en https://sso.contoso.local/realms/contoso/protocol/saml/descriptor.
4. Active Directory por LDAP, en solo lectura
- READ_ONLY: Keycloak no escribe nada en AD. Las altas, bajas y cambios de contraseña siguen en el directorio.
- El filtro deja fuera las cuentas deshabilitadas en AD. Una baja deja de entrar en cuanto se deshabilita, porque la contraseña se valida contra AD en cada inicio de sesión.
- La primera vez, Action › Sync all users. Después, la sincronización periódica de cambios.
5. Grupos, roles y lo que llega en el token
- Grupos de AD: en el proveedor LDAP, pestaña Mappers › Add mapper, tipo
group-ldap-mapper: LDAP Groups DNOU=Grupos,DC=contoso,DC=local, Group Object Classesgroup, Membership LDAP Attributemember, ModeREAD_ONLY. Si hay grupos anidados, User Groups Retrieve StrategyLOAD_GROUPS_BY_MEMBER_ATTRIBUTE_RECURSIVELY. Después, Action › Sync LDAP groups to Keycloak. - Roles de la aplicación: en Clients › compras › Roles,
compradoryaprobador. La aplicación decide por roles, no por nombres de grupos de AD que alguien puede renombrar. - Grupo a rol: Groups › GG-Compras-Aprobadores › Role mapping › Assign role, filtro por clientes,
compras aprobador. - Comprobar: Clients › compras › Client scopes › Evaluate, elegir un usuario de prueba y ver Generated access token. Los roles salen en
resource_access.compras.roles. Si la aplicación quiere también los grupos, un mapper Group Membership en el ámbito dedicado del cliente (compras-dedicated) con Token Claim Namegroups.
6. OTP obligatorio
- Authentication › Policies › OTP Policy: TOTP, 6 dígitos, 30 segundos, Look around window 1. Valen Microsoft Authenticator, Google Authenticator o FreeOTP.
- Authentication › Flows: duplicar
browsercomobrowser-otp. En la copia, el subflujo condicional del segundo factor (su nombre empieza por Browser - Conditional) y su OTP Form pasan a Required. - Piloto: en Clients › compras › Advanced › Authentication flow overrides, Browser Flow
browser-otp. Solo esa aplicación pide OTP. - Todos: en el flujo, Action › Bind flow › Browser flow. Quien no tenga OTP verá al entrar la pantalla para registrar la aplicación del móvil.
Móvil perdido: Users › usuario › Credentials, borrar la credencial OTP tras verificar la identidad por el procedimiento del service desk; en el siguiente inicio de sesión la vuelve a registrar.
7. El ticket: "compras devuelve Invalid parameter: redirect_uri"
Después de la actualización de la aplicación de compras, nadie entra: Keycloak muestra We are sorry... Invalid parameter: redirect_uri.
- La URL que pide la aplicación: en la barra del navegador, el parámetro
redirect_uride.../realms/contoso/protocol/openid-connect/auth?client_id=compras&redirect_uri=.... Descodificado:http://compras.contoso.local/oidc/callback. - El log de Keycloak lo confirma:
docker logs keycloak 2>&1 | grep invalid_redirect_uri | tail -1WARN [org.keycloak.events] (executor-thread-12) type="LOGIN_ERROR", realmId="6f1c2e0a-...", clientId="compras", userId="null", ipAddress="10.20.5.31", error="invalid_redirect_uri", redirect_uri="http://compras.contoso.local/oidc/callback"- Comparar con Valid redirect URIs del cliente, carácter a carácter. La comparación es exacta:
| Lo que manda la aplicación | Lo registrado | Causa |
|---|---|---|
http://... | https://... | La aplicación, detrás de su propio proxy, no se fía de X-Forwarded-Proto y cree que está en HTTP |
https://compras01.contoso.local/... | https://compras.contoso.local/... | Se entra por el nombre del servidor o por la IP |
.../oidc/callback/ | .../oidc/callback | Barra final o ruta nueva tras la actualización |
client_id=compras-test | compras | Configuración de otro entorno |
- Arreglar en el sitio correcto: aquí, la causa es la primera; la actualización ha perdido el ajuste que hacía confiar en el proxy, y el equipo de la aplicación lo restaura. Añadir la URI
http://en Keycloak lo taparía y dejaría el código de autorización viajando sin cifrar. Añadir una URI nueva solo tiene sentido si la aplicación de verdad cambió de dirección, y entonces con la URI exacta, nunca*. - Si el error sale al cerrar sesión, es el
post_logout_redirect_uriy se compara con Valid post logout redirect URIs.
En el ticket: la URI recibida, la registrada, la causa y quién cambió qué.
La hora de los servidores: TOTP depende del reloj. Si el servidor de Keycloak se desvía más de lo que cubre Look around window, los códigos correctos se rechazan. Y si el reloj del servidor de la aplicación va adelantado o atrasado, rechaza tokens recién emitidos (iat en el futuro, exp ya pasado) o aserciones SAML fuera de NotBefore/NotOnOrAfter. Antes de tocar márgenes de tolerancia en la aplicación, chronyc tracking o w32tm /query /status en los dos lados.
Siguientes pasos
- La guía de Entra ID SSO con SAML hace lo mismo para aplicaciones SaaS con identidades en la nube; las dos conviven.
- El proxy inverso, las cabeceras
X-Forwarded-*y los certificados están en la de Nginx y Apache. - Copia de la base de datos PostgreSQL de Keycloak y exportación del realm (
kc.sh export) antes de cada actualización de versión.
¿Falta algo o en tu entorno es distinto? Escríbeme y lo corrijo.
Tech Career Lab · newsletter
¿Te ha servido? Cada dos semanas envío una idea práctica para perfiles IT: LinkedIn, proyectos, networking y procesos de selección.
Apúntate y llévate las 33 ideas →