From 1ca16b0e941c49b2854e2139bcab0fc007dbca63 Mon Sep 17 00:00:00 2001 From: luis cespedes Date: Tue, 13 May 2025 17:06:16 -0400 Subject: [PATCH] keyclock --- tutorial keycloack.md | 963 --------------------- tutorial-keycloak-completo.md | 1476 +++++++++++++++++++++++++++++++++ 2 files changed, 1476 insertions(+), 963 deletions(-) delete mode 100644 tutorial keycloack.md create mode 100644 tutorial-keycloak-completo.md diff --git a/tutorial keycloack.md b/tutorial keycloack.md deleted file mode 100644 index 2d94751..0000000 --- a/tutorial keycloack.md +++ /dev/null @@ -1,963 +0,0 @@ -# Tutorial: Implementación de Keycloak con LDAP para Angular - -Este tutorial explica cómo configurar un sistema de autenticación completo utilizando Keycloak como proveedor de identidad, OpenLDAP como directorio de usuarios y una aplicación Angular que consume estos servicios. - -## Índice - -1. [Preparación del entorno Ubuntu](#1-preparaci%C3%B3n-del-entorno-ubuntu) -2. [Instalación y configuración de OpenLDAP](#2-instalaci%C3%B3n-y-configuraci%C3%B3n-de-openldap) -3. [Crear estructura LDAP para correos.com](#3-crear-estructura-ldap-para-correoscom) -4. [Instalación y configuración de Keycloak](#4-instalaci%C3%B3n-y-configuraci%C3%B3n-de-keycloak) -5. [Configuración de Keycloak en la interfaz web](#5-configuraci%C3%B3n-de-keycloak-en-la-interfaz-web) -6. [Configuración de una aplicación Angular](#6-configuraci%C3%B3n-de-una-aplicaci%C3%B3n-angular) -7. [Arquitectura del sistema](#7-arquitectura-del-sistema) -8. [Resolución de problemas comunes](#8-resoluci%C3%B3n-de-problemas-comunes) -9. [Verificación y prueba del sistema](#9-verificaci%C3%B3n-y-prueba-del-sistema) -10. [Resumen](#10-resumen) - -## 1. Preparación del entorno Ubuntu - -Primero, actualizamos el sistema e instalamos Java: - -```bash -sudo apt update -sudo apt upgrade -y -sudo apt install openjdk-17-jdk -y - -``` - -Verifica la instalación de Java: - -```bash -java -version - -``` - -## 2. Instalación y configuración de OpenLDAP - -### Instalar OpenLDAP y utilidades - -```bash -sudo apt install slapd ldap-utils -y - -``` - -Durante la instalación, se te pedirá configurar una contraseña de administrador para LDAP. - -### Reconfigurar LDAP con el dominio correcto - -```bash -sudo dpkg-reconfigure slapd - -``` - -En la configuración: - -1. "¿Omitir configuración del servidor LDAP?" → No -2. "Nombre de dominio DNS:" → **correos.com** -3. "Nombre de la organización:" → Correos Org -4. "Contraseña de administrador:" → [tu contraseña segura] -5. "Confirmar contraseña:" → [repetir la contraseña] -6. "Motor de base de datos:" → MDB -7. "¿Quiere que se elimine la base de datos cuando se purgue slapd?" → No -8. "¿Mover la base de datos antigua?" → Sí - -### Verificar que LDAP se esté ejecutando correctamente - -```bash -sudo systemctl status slapd - -``` - -### Comprobar la conexión LDAP básica - -```bash -ldapsearch -x -H ldap://localhost -b dc=correos,dc=com -D "cn=admin,dc=correos,dc=com" -W - -``` - -### Instalar phpLDAPadmin para la gestión gráfica - -```bash -sudo apt install phpldapadmin -y - -``` - -### Configurar phpLDAPadmin - -Edita el archivo de configuración: - -```bash -sudo nano /etc/phpldapadmin/config.php - -``` - -Busca y modifica las siguientes líneas: - -```php -$servers->setValue('server','host','127.0.0.1'); -$servers->setValue('server','base',array('dc=correos,dc=com')); -$servers->setValue('login','bind_id','cn=admin,dc=correos,dc=com'); - -``` - -Y cambia esta línea: - -```php -$servers->setValue('login','anon_bind',true); - -``` - -por: - -```php -$servers->setValue('login','anon_bind',false); - -``` - -Reinicia el servidor web: - -```bash -sudo systemctl restart apache2 - -``` - -## 3. Crear estructura LDAP para correos.com - -### Crear unidades organizativas - -Crea un archivo para las unidades organizativas: - -```bash -nano ~/ou.ldif - -``` - -Con el siguiente contenido: - -```ldif -dn: ou=grupos,dc=correos,dc=com -objectClass: organizationalUnit -ou: grupos - -dn: ou=usuarios,dc=correos,dc=com -objectClass: organizationalUnit -ou: usuarios - -``` - -Aplica los cambios: - -```bash -ldapadd -x -D cn=admin,dc=correos,dc=com -W -f ~/ou.ldif - -``` - -### Crear grupos LDAP - -Crea un archivo para los grupos: - -```bash -nano ~/grupos.ldif - -``` - -Con el siguiente contenido: - -```ldif -dn: cn=administradores,ou=grupos,dc=correos,dc=com -objectClass: posixGroup -cn: administradores -gidNumber: 1000 - -dn: cn=desarrolladores,ou=grupos,dc=correos,dc=com -objectClass: posixGroup -cn: desarrolladores -gidNumber: 1001 - -dn: cn=usuarios,ou=grupos,dc=correos,dc=com -objectClass: posixGroup -cn: usuarios -gidNumber: 1002 - -``` - -Aplica los cambios: - -```bash -ldapadd -x -D cn=admin,dc=correos,dc=com -W -f ~/grupos.ldif - -``` - -### Crear usuarios LDAP - -Primero, genera contraseñas encriptadas para los usuarios: - -```bash -slappasswd -s "password123" - -``` - -Anota el hash resultante para usarlo en el siguiente archivo. - -Crea un archivo para los usuarios: - -```bash -nano ~/usuarios.ldif - -``` - -Con el siguiente contenido (reemplazando {HASH} con el hash que generaste): - -```ldif -dn: uid=admin,ou=usuarios,dc=correos,dc=com -objectClass: inetOrgPerson -objectClass: posixAccount -objectClass: shadowAccount -uid: admin -sn: Admin -givenName: Admin -cn: Admin User -displayName: Admin User -uidNumber: 1000 -gidNumber: 1000 -userPassword: {HASH} -loginShell: /bin/bash -homeDirectory: /home/admin -mail: admin@correos.com - -dn: uid=developer,ou=usuarios,dc=correos,dc=com -objectClass: inetOrgPerson -objectClass: posixAccount -objectClass: shadowAccount -uid: developer -sn: Developer -givenName: Dev -cn: Dev User -displayName: Developer User -uidNumber: 1001 -gidNumber: 1001 -userPassword: {HASH} -loginShell: /bin/bash -homeDirectory: /home/developer -mail: developer@correos.com - -dn: uid=user,ou=usuarios,dc=correos,dc=com -objectClass: inetOrgPerson -objectClass: posixAccount -objectClass: shadowAccount -uid: user -sn: User -givenName: Normal -cn: Normal User -displayName: Normal User -uidNumber: 1002 -gidNumber: 1002 -userPassword: {HASH} -loginShell: /bin/bash -homeDirectory: /home/user -mail: user@correos.com - -``` - -Aplica los cambios: - -```bash -ldapadd -x -D cn=admin,dc=correos,dc=com -W -f ~/usuarios.ldif - -``` - -### Asociar usuarios a grupos - -Crea un archivo para las membresías: - -```bash -nano ~/miembros.ldif - -``` - -Con el siguiente contenido: - -```ldif -dn: cn=administradores,ou=grupos,dc=correos,dc=com -changetype: modify -add: memberUid -memberUid: admin - -dn: cn=desarrolladores,ou=grupos,dc=correos,dc=com -changetype: modify -add: memberUid -memberUid: developer - -dn: cn=usuarios,ou=grupos,dc=correos,dc=com -changetype: modify -add: memberUid -memberUid: user - -``` - -Aplica los cambios: - -```bash -ldapmodify -x -D cn=admin,dc=correos,dc=com -W -f ~/miembros.ldif - -``` - -### Verificar la estructura LDAP - -```bash -ldapsearch -x -H ldap://localhost -b dc=correos,dc=com -D "cn=admin,dc=correos,dc=com" -W - -``` - -## 4. Instalación y configuración de Keycloak - -### Descargar e instalar Keycloak - -```bash -# Crear directorio para Keycloak -mkdir -p ~/keycloak -cd ~/keycloak - -# Descargar la última versión de Keycloak -wget https://github.com/keycloak/keycloak/releases/download/21.1.1/keycloak-21.1.1.tar.gz - -# Extraer el archivo -tar -xvzf keycloak-21.1.1.tar.gz - -# Mover a una ubicación más adecuada -sudo mv keycloak-21.1.1 /opt/keycloak - -# Crear un usuario para Keycloak -sudo useradd -r -s /sbin/nologin keycloak - -# Asignar permisos -sudo chown -R keycloak:keycloak /opt/keycloak - -``` - -### Configurar usuario administrador inicial para Keycloak - -Crea un archivo de propiedades: - -```bash -sudo nano /opt/keycloak/conf/keycloak.conf - -``` - -Agrega estas líneas: - -``` -# Configuración básica -http-port=8080 -https-port=8443 -hostname=localhost - -# Configuración de administrador inicial -http-enabled=true - -``` - -### Iniciar Keycloak en modo desarrollo - -```bash -cd /opt/keycloak -export KEYCLOAK_ADMIN=admin -export KEYCLOAK_ADMIN_PASSWORD=admin -sudo -u keycloak bin/kc.sh start-dev - -``` - -Esto iniciará Keycloak con un usuario administrador "admin" y contraseña "admin". - -### Configurar Keycloak como servicio - -En otra terminal, crea un archivo de servicio systemd: - -```bash -sudo nano /etc/systemd/system/keycloak.service - -``` - -Con el siguiente contenido: - -```ini -[Unit] -Description=Keycloak Application Server -After=network.target - -[Service] -Type=idle -User=keycloak -Group=keycloak -Environment="KEYCLOAK_ADMIN=admin" -Environment="KEYCLOAK_ADMIN_PASSWORD=admin" -ExecStart=/opt/keycloak/bin/kc.sh start-dev -TimeoutStartSec=600 -TimeoutStopSec=600 - -[Install] -WantedBy=multi-user.target - -``` - -En el futuro, podrás habilitar e iniciar el servicio con: - -```bash -sudo systemctl daemon-reload -sudo systemctl enable keycloak -sudo systemctl start keycloak - -``` - -## 5. Configuración de Keycloak en la interfaz web - -Ahora puedes acceder a la consola de administración de Keycloak en http://localhost:8080/admin/ e iniciar sesión con: - -- Usuario: `admin` -- Contraseña: `admin` - -### Crear un nuevo Reino (Realm) - -1. Haz clic en el menú desplegable superior izquierdo que dice "master" -2. Selecciona "Create Realm" -3. Ingresa el nombre: `angular-app` -4. Haz clic en "Create" - -### Configurar la Federación de Usuarios LDAP - -1. En el menú lateral izquierdo, selecciona "User Federation" - -2. Haz clic en "Add provider" → "ldap" - -3. Completa los siguientes campos: - - - Console Display Name: `LDAP` - - Vendor: `Other` - - Connection URL: `ldap://localhost:389` - - Enable StartTLS: `OFF` - - Bind Type: `simple` - - Bind DN: `cn=admin,dc=correos,dc=com` - - Bind Credential: (la contraseña de admin LDAP) - - Edit Mode: `WRITABLE` - - Users DN: `ou=usuarios,dc=correos,dc=com` - - Username LDAP attribute: `uid` - - RDN LDAP attribute: `uid` - - UUID LDAP attribute: `entryUUID` - - User Object Classes: `inetOrgPerson, posixAccount` - - Custom User LDAP Filter: (dejar en blanco) - - Search Scope: `One Level` -4. Haz clic en "Test connection" y "Test authentication" para verificar - -5. Guarda la configuración - -6. En la pantalla del proveedor LDAP, ve a la pestaña "Synchronization" - -7. Haz clic en "Sync all users" - - -### Configurar el Mapeo de Grupos LDAP - -1. En la pantalla del proveedor LDAP, ve a la pestaña "Mappers" -2. Haz clic en "Create" -3. Completa: - - Name: `group-mapper` - - Mapper Type: `group-ldap-mapper` - - LDAP Groups DN: `ou=grupos,dc=correos,dc=com` - - Group Object Classes: `posixGroup` - - Membership LDAP Attribute: `memberUid` - - Group Name LDAP Attribute: `cn` - - User Roles Retrieve Strategy: `LOAD_GROUPS_BY_MEMBER_ATTRIBUTE` - - Member-Of LDAP Attribute: `memberOf` - - Mapped Group Attributes: (dejar en blanco) - - Drop non-existing groups during sync: `ON` -4. Haz clic en "Save" -5. En la pantalla del mapper, haz clic en "Sync LDAP Groups to Keycloak" - -### Crear un Cliente para Angular - -1. En el menú lateral izquierdo, selecciona "Clients" - -2. Haz clic en "Create client" - -3. Completa: - - - Client type: `OpenID Connect` - - Client ID: `angular-app` - - Name: `Angular Application` - - Haz clic en "Next" -4. En la siguiente pantalla: - - - Client authentication: `ON` - - Authorization: `OFF` - - Haz clic en "Next" -5. En la siguiente pantalla: - - - Root URL: `http://localhost:4200` - - Home URL: `/` - - Valid redirect URIs: `http://localhost:4200/*` - - Web origins: `http://localhost:4200` - - Haz clic en "Save" -6. En la pestaña "Credentials" del cliente, copia el "Client secret" (lo necesitarás para la aplicación Angular) - - -## 6. Configuración de una aplicación Angular - -Primero, instala Node.js y npm: - -```bash -curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - -sudo apt-get install -y nodejs - -``` - -### Crear una aplicación Angular - -```bash -# Instalar Angular CLI -npm install -g @angular/cli - -# Crear una nueva aplicación -ng new angular-keycloak-app -cd angular-keycloak-app - -# Instalar la biblioteca para integrar Keycloak -npm install keycloak-angular keycloak-js - -``` - -### Configurar Keycloak en Angular - -Crea un archivo de configuración en `src/assets/keycloak.json`: - -```json -{ - "realm": "angular-app", - "auth-server-url": "http://localhost:8080/", - "resource": "angular-app", - "credentials": { - "secret": "TU_CLIENT_SECRET_AQUÍ" - } -} - -``` - -Modifica el archivo `src/app/app.module.ts`: - -```typescript -import { NgModule, APP_INITIALIZER } from '@angular/core'; -import { BrowserModule } from '@angular/platform-browser'; -import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular'; -import { AppRoutingModule } from './app-routing.module'; -import { AppComponent } from './app.component'; - -function initializeKeycloak(keycloak: KeycloakService) { - return () => - keycloak.init({ - config: { - url: 'http://localhost:8080', - realm: 'angular-app', - clientId: 'angular-app' - }, - initOptions: { - onLoad: 'check-sso', - silentCheckSsoRedirectUri: - window.location.origin + '/assets/silent-check-sso.html' - } - }); -} - -@NgModule({ - declarations: [ - AppComponent - ], - imports: [ - BrowserModule, - AppRoutingModule, - KeycloakAngularModule - ], - providers: [ - { - provide: APP_INITIALIZER, - useFactory: initializeKeycloak, - multi: true, - deps: [KeycloakService] - } - ], - bootstrap: [AppComponent] -}) -export class AppModule { } - -``` - -Crea un archivo `src/assets/silent-check-sso.html`: - -```html - - - - - - -``` - -### Crear un servicio de autenticación - -```bash -ng generate service auth - -``` - -Edita `src/app/auth.service.ts`: - -```typescript -import { Injectable } from '@angular/core'; -import { KeycloakService } from 'keycloak-angular'; -import { KeycloakProfile } from 'keycloak-js'; - -@Injectable({ - providedIn: 'root' -}) -export class AuthService { - - constructor(private keycloak: KeycloakService) { } - - public getLoggedUser(): Promise { - return this.keycloak.loadUserProfile(); - } - - public login(): void { - this.keycloak.login(); - } - - public logout(): void { - this.keycloak.logout(); - } - - public isLoggedIn(): Promise { - return this.keycloak.isLoggedIn(); - } - - public getRoles(): string[] { - return this.keycloak.getUserRoles(); - } -} - -``` - -### Crear un guardia para rutas protegidas - -```bash -ng generate guard auth - -``` - -Edita `src/app/auth.guard.ts`: - -```typescript -import { Injectable } from '@angular/core'; -import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router'; -import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular'; - -@Injectable({ - providedIn: 'root' -}) -export class AuthGuard extends KeycloakAuthGuard { - - constructor( - protected override readonly router: Router, - protected readonly keycloak: KeycloakService - ) { - super(router, keycloak); - } - - public async isAccessAllowed( - route: ActivatedRouteSnapshot, - state: RouterStateSnapshot - ): Promise { - - // Verifica si el usuario está autenticado - if (!this.authenticated) { - await this.keycloak.login({ - redirectUri: window.location.origin + state.url, - }); - return false; - } - - // Obtiene los roles requeridos desde la ruta - const requiredRoles = route.data['roles']; - - // Permite el acceso si no hay roles requeridos - if (!requiredRoles || requiredRoles.length === 0) { - return true; - } - - // Verifica si el usuario tiene al menos uno de los roles requeridos - return requiredRoles.some((role: string) => this.roles.includes(role)); - } -} - -``` - -### Configurar las rutas con protección - -Modifica `src/app/app-routing.module.ts`: - -```typescript -import { NgModule } from '@angular/core'; -import { RouterModule, Routes } from '@angular/router'; -import { AuthGuard } from './auth.guard'; -import { AppComponent } from './app.component'; - -const routes: Routes = [ - { - path: 'admin', - component: AppComponent, - canActivate: [AuthGuard], - data: { roles: ['administradores'] } - }, - { - path: 'developer', - component: AppComponent, - canActivate: [AuthGuard], - data: { roles: ['desarrolladores'] } - }, - { - path: 'user', - component: AppComponent, - canActivate: [AuthGuard], - data: { roles: ['usuarios'] } - }, - { - path: '', - component: AppComponent - } -]; - -@NgModule({ - imports: [RouterModule.forRoot(routes)], - exports: [RouterModule] -}) -export class AppRoutingModule { } - -``` - -### Modificar el componente principal - -Edita `src/app/app.component.ts`: - -```typescript -import { Component, OnInit } from '@angular/core'; -import { AuthService } from './auth.service'; -import { KeycloakProfile } from 'keycloak-js'; - -@Component({ - selector: 'app-root', - templateUrl: './app.component.html', - styleUrls: ['./app.component.css'] -}) -export class AppComponent implements OnInit { - title = 'angular-keycloak-app'; - isLoggedIn = false; - userProfile: KeycloakProfile | null = null; - userRoles: string[] = []; - - constructor(private authService: AuthService) {} - - async ngOnInit() { - this.isLoggedIn = await this.authService.isLoggedIn(); - - if (this.isLoggedIn) { - this.userProfile = await this.authService.getLoggedUser(); - this.userRoles = this.authService.getRoles(); - } - } - - login() { - this.authService.login(); - } - - logout() { - this.authService.logout(); - } -} - -``` - -Edita `src/app/app.component.html`: - -```html -
-
-
-
-
-

Demo Keycloak con LDAP - Correos.com

-
-
-
-

Bienvenido, {{ userProfile?.firstName }} {{ userProfile?.lastName }}

-

Email: {{ userProfile?.email }}

-

Nombre de Usuario: {{ userProfile?.username }}

- -

Tus Roles:

-
    -
  • {{ role }}
  • -
- - -
- - -

Por favor inicia sesión para acceder al sistema

- -
-
-
-
-
-
- -``` - -Agrega Bootstrap a `src/index.html`: - -```html - - - - - -``` - -### Ejecutar la aplicación - -```bash -ng serve - -``` - -Abre http://localhost:4200 en tu navegador. - -## 7. Arquitectura del sistema - -La arquitectura del sistema está compuesta por los siguientes componentes: - -- **OpenLDAP**: Directorio de usuarios y grupos (puerto 389) -- **phpLDAPadmin**: Interfaz gráfica para administrar LDAP (puerto 80) -- **Keycloak**: Servidor de autenticación y autorización (puerto 8080) -- **Aplicación Angular**: Cliente que utiliza el SSO de Keycloak (puerto 4200) - -La estructura del directorio LDAP para correos.com es: - -- dc=correos,dc=com (raíz del directorio) - - ou=usuarios (unidad organizativa para usuarios) - - uid=admin (usuario administrador) - - uid=developer (usuario desarrollador) - - uid=user (usuario normal) - - ou=grupos (unidad organizativa para grupos) - - cn=administradores (grupo de administradores) - - cn=desarrolladores (grupo de desarrolladores) - - cn=usuarios (grupo de usuarios) - -## 8. Resolución de problemas comunes - -### Problema: "ldap_bind: Invalid credentials (49)" - -Este error ocurre cuando intentas conectarte a LDAP con credenciales incorrectas. Para resolverlo: - -1. Verifica que estás usando el dominio correcto: `dc=correos,dc=com` -2. Asegúrate de usar el DN correcto: `cn=admin,dc=correos,dc=com` -3. Comprueba que la contraseña de administrador es correcta - -Si olvidaste la contraseña, puedes restablecerla: - -```bash -sudo dpkg-reconfigure slapd - -``` - -O también: - -```bash -sudo slappasswd -# Copia el hash generado -sudo nano /etc/ldap/slapd.d/cn=config/olcDatabase={1}mdb.ldif -# Busca olcRootPW y reemplaza el valor con el nuevo hash -sudo systemctl restart slapd - -``` - -### Problema: No puedes conectarte a LDAP en absoluto - -```bash -# Verifica que el servicio esté ejecutándose -sudo systemctl status slapd - -# Reinicia el servicio si es necesario -sudo systemctl restart slapd - -# Verifica que el puerto esté abierto -sudo netstat -tulpn | grep 389 - -``` - -### Problema: Keycloak no se inicia - -```bash -# Verifica los logs -sudo journalctl -u keycloak - -# Asegúrate de que Java esté instalado correctamente -java -version - -# Verifica los permisos -sudo ls -la /opt/keycloak - -``` - -### Problema: La aplicación Angular no puede conectarse a Keycloak - -1. Verifica que Keycloak esté en ejecución -2. Comprueba la URL del servidor Keycloak en la configuración de Angular -3. Asegúrate de que el cliente esté correctamente configurado en Keycloak -4. Verifica el Client Secret -5. Comprueba las URLs de redirección - -## 9. Verificación y prueba del sistema - -### Verificar que Keycloak esté sincronizando correctamente con LDAP - -1. Inicia sesión en la consola de administración de Keycloak (http://localhost:8080/admin/) -2. Navega a "Users" en el reino "angular-app" -3. Deberías ver los usuarios de LDAP: admin, developer y user -4. Navega a "Groups" y verifica que los grupos de LDAP estén presentes - -### Probar la aplicación Angular - -1. Asegúrate de que Keycloak esté ejecutándose -2. Inicia la aplicación Angular con `ng serve` -3. Navega a http://localhost:4200 -4. Haz clic en "Iniciar Sesión" -5. Deberías ser redirigido a la pantalla de inicio de sesión de Keycloak -6. Inicia sesión con uno de los usuarios LDAP: - - Usuario: admin, Contraseña: password123 - - Usuario: developer, Contraseña: password123 - - Usuario: user, Contraseña: password123 -7. Después de iniciar sesión, serás redirigido de vuelta a la aplicación -8. La aplicación mostrará tu perfil y roles - -## 10. Resumen - -Esta configuración proporciona un sistema completo de gestión de identidades y acceso con: - -- **Autenticación centralizada**: Los usuarios solo necesitan recordar un conjunto de credenciales -- **Gestión de usuarios unificada**: Todos los usuarios se administran en LDAP -- **Control de acceso basado en roles**: Las rutas y funcionalidades pueden ser protegidas según los roles del usuario -- **Experiencia de inicio de sesión único (SSO)**: Una vez autenticado, el usuario puede acceder a todas las aplicaciones integradas - -Este sistema es adecuado para entornos empresariales donde se requiere un control de acceso detallado y una gestión centralizada de usuarios. - -La implementación es nativa en un sistema Ubuntu, lo que la hace ideal para despliegues en servidores VPS o entornos similares sin necesidad de contenedores. diff --git a/tutorial-keycloak-completo.md b/tutorial-keycloak-completo.md new file mode 100644 index 0000000..efba6fc --- /dev/null +++ b/tutorial-keycloak-completo.md @@ -0,0 +1,1476 @@ +# Tutorial Completo: Implementación de Keycloak con LDAP e Integración con Angular + +Este tutorial comprensivo explica cómo configurar un sistema de autenticación completo utilizando Keycloak como proveedor de identidad, OpenLDAP como directorio de usuarios, y cómo integrar este sistema con una aplicación Angular moderna. La guía cubre desde la configuración del entorno básico hasta las técnicas más avanzadas de integración con Angular 19. + +## Índice + +1. [Preparación del entorno](#1-preparación-del-entorno) +2. [Instalación y configuración de OpenLDAP](#2-instalación-y-configuración-de-openldap) +3. [Crear estructura LDAP para usuarios y grupos](#3-crear-estructura-ldap-para-usuarios-y-grupos) +4. [Instalación y configuración de Keycloak](#4-instalación-y-configuración-de-keycloak) +5. [Configuración de Keycloak en la interfaz web](#5-configuración-de-keycloak-en-la-interfaz-web) +6. [Integración con Angular: Enfoque básico](#6-integración-con-angular-enfoque-básico) +7. [Integración con Angular 19: Enfoque moderno](#7-integración-con-angular-19-enfoque-moderno) +8. [Servicios de autenticación avanzados](#8-servicios-de-autenticación-avanzados) +9. [Guardias de ruta e interceptores HTTP](#9-guardias-de-ruta-e-interceptores-http) +10. [Componentes de UI para login/logout](#10-componentes-de-ui-para-loginlogout) +11. [Arquitectura del sistema](#11-arquitectura-del-sistema) +12. [Resolución de problemas comunes](#12-resolución-de-problemas-comunes) +13. [Verificación y prueba del sistema](#13-verificación-y-prueba-del-sistema) +14. [Recursos adicionales](#14-recursos-adicionales) +15. [Resumen](#15-resumen) + +## 1. Preparación del entorno + +### Requisitos del sistema + +Para seguir este tutorial, necesitarás: + +- Un sistema Ubuntu Server o una distribución similar de Linux +- Acceso de root o privilegios sudo +- Node.js (versión 18.x o superior) +- Angular CLI (versión 19.x o compatible) +- Java JDK (OpenJDK 17 o superior) + +### Actualización del sistema e instalación de Java + +Primero, actualiza el sistema e instala Java: + +```bash +sudo apt update +sudo apt upgrade -y +sudo apt install openjdk-17-jdk -y +``` + +Verifica la instalación de Java: + +```bash +java -version +``` + +### Instalación de Node.js y Angular CLI + +Para el desarrollo de aplicaciones Angular, instala Node.js y Angular CLI: + +```bash +# Instalar Node.js +curl -fsSL https://deb.nodesource.com/setup_18.x | sudo -E bash - +sudo apt-get install -y nodejs + +# Verificar la instalación +node -v +npm -v + +# Instalar Angular CLI +npm install -g @angular/cli + +# Verificar la instalación +ng version +``` + +## 2. Instalación y configuración de OpenLDAP + +### Instalar OpenLDAP y utilidades + +```bash +sudo apt install slapd ldap-utils -y +``` + +Durante la instalación, se te pedirá configurar una contraseña de administrador para LDAP. + +### Reconfigurar LDAP con el dominio correcto + +```bash +sudo dpkg-reconfigure slapd +``` + +En la configuración: + +1. "¿Omitir configuración del servidor LDAP?" → No +2. "Nombre de dominio DNS:" → **correos.com** +3. "Nombre de la organización:" → Correos Org +4. "Contraseña de administrador:" → [tu contraseña segura] +5. "Confirmar contraseña:" → [repetir la contraseña] +6. "Motor de base de datos:" → MDB +7. "¿Quiere que se elimine la base de datos cuando se purgue slapd?" → No +8. "¿Mover la base de datos antigua?" → Sí + +### Verificar que LDAP se esté ejecutando correctamente + +```bash +sudo systemctl status slapd +``` + +### Comprobar la conexión LDAP básica + +```bash +ldapsearch -x -H ldap://localhost -b dc=correos,dc=com -D "cn=admin,dc=correos,dc=com" -W +``` + +### Instalar phpLDAPadmin para la gestión gráfica + +```bash +sudo apt install phpldapadmin -y +``` + +### Configurar phpLDAPadmin + +Edita el archivo de configuración: + +```bash +sudo nano /etc/phpldapadmin/config.php +``` + +Busca y modifica las siguientes líneas: + +```php +$servers->setValue('server','host','127.0.0.1'); +$servers->setValue('server','base',array('dc=correos,dc=com')); +$servers->setValue('login','bind_id','cn=admin,dc=correos,dc=com'); +``` + +Y cambia esta línea: + +```php +$servers->setValue('login','anon_bind',true); +``` + +por: + +```php +$servers->setValue('login','anon_bind',false); +``` + +Reinicia el servidor web: + +```bash +sudo systemctl restart apache2 +``` + +## 3. Crear estructura LDAP para usuarios y grupos + +### Crear unidades organizativas + +Crea un archivo para las unidades organizativas: + +```bash +nano ~/ou.ldif +``` + +Con el siguiente contenido: + +```ldif +dn: ou=grupos,dc=correos,dc=com +objectClass: organizationalUnit +ou: grupos + +dn: ou=usuarios,dc=correos,dc=com +objectClass: organizationalUnit +ou: usuarios +``` + +Aplica los cambios: + +```bash +ldapadd -x -D cn=admin,dc=correos,dc=com -W -f ~/ou.ldif +``` + +### Crear grupos LDAP + +Crea un archivo para los grupos: + +```bash +nano ~/grupos.ldif +``` + +Con el siguiente contenido: + +```ldif +dn: cn=administradores,ou=grupos,dc=correos,dc=com +objectClass: posixGroup +cn: administradores +gidNumber: 1000 + +dn: cn=desarrolladores,ou=grupos,dc=correos,dc=com +objectClass: posixGroup +cn: desarrolladores +gidNumber: 1001 + +dn: cn=usuarios,ou=grupos,dc=correos,dc=com +objectClass: posixGroup +cn: usuarios +gidNumber: 1002 +``` + +Aplica los cambios: + +```bash +ldapadd -x -D cn=admin,dc=correos,dc=com -W -f ~/grupos.ldif +``` + +### Crear usuarios LDAP + +Primero, genera contraseñas encriptadas para los usuarios: + +```bash +slappasswd -s "password123" +``` + +Anota el hash resultante para usarlo en el siguiente archivo. + +Crea un archivo para los usuarios: + +```bash +nano ~/usuarios.ldif +``` + +Con el siguiente contenido (reemplazando {HASH} con el hash que generaste): + +```ldif +dn: uid=admin,ou=usuarios,dc=correos,dc=com +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: admin +sn: Admin +givenName: Admin +cn: Admin User +displayName: Admin User +uidNumber: 1000 +gidNumber: 1000 +userPassword: {HASH} +loginShell: /bin/bash +homeDirectory: /home/admin +mail: admin@correos.com + +dn: uid=developer,ou=usuarios,dc=correos,dc=com +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: developer +sn: Developer +givenName: Dev +cn: Dev User +displayName: Developer User +uidNumber: 1001 +gidNumber: 1001 +userPassword: {HASH} +loginShell: /bin/bash +homeDirectory: /home/developer +mail: developer@correos.com + +dn: uid=user,ou=usuarios,dc=correos,dc=com +objectClass: inetOrgPerson +objectClass: posixAccount +objectClass: shadowAccount +uid: user +sn: User +givenName: Normal +cn: Normal User +displayName: Normal User +uidNumber: 1002 +gidNumber: 1002 +userPassword: {HASH} +loginShell: /bin/bash +homeDirectory: /home/user +mail: user@correos.com +``` + +Aplica los cambios: + +```bash +ldapadd -x -D cn=admin,dc=correos,dc=com -W -f ~/usuarios.ldif +``` + +### Asociar usuarios a grupos + +Crea un archivo para las membresías: + +```bash +nano ~/miembros.ldif +``` + +Con el siguiente contenido: + +```ldif +dn: cn=administradores,ou=grupos,dc=correos,dc=com +changetype: modify +add: memberUid +memberUid: admin + +dn: cn=desarrolladores,ou=grupos,dc=correos,dc=com +changetype: modify +add: memberUid +memberUid: developer + +dn: cn=usuarios,ou=grupos,dc=correos,dc=com +changetype: modify +add: memberUid +memberUid: user +``` + +Aplica los cambios: + +```bash +ldapmodify -x -D cn=admin,dc=correos,dc=com -W -f ~/miembros.ldif +``` + +### Verificar la estructura LDAP + +```bash +ldapsearch -x -H ldap://localhost -b dc=correos,dc=com -D "cn=admin,dc=correos,dc=com" -W +``` + +## 4. Instalación y configuración de Keycloak + +### Descargar e instalar Keycloak + +```bash +# Crear directorio para Keycloak +mkdir -p ~/keycloak +cd ~/keycloak + +# Descargar la última versión de Keycloak +wget https://github.com/keycloak/keycloak/releases/download/26.2.4/keycloak-26.2.4.tar.gz + +# Extraer el archivo +tar -xvzf keycloak-26.2.4.tar.gz + +# Mover a una ubicación más adecuada +sudo mv keycloak-26.2.4 /opt/keycloak + +# Crear un usuario para Keycloak +sudo useradd -r -s /sbin/nologin keycloak + +# Asignar permisos +sudo chown -R keycloak:keycloak /opt/keycloak +``` + +### Configurar usuario administrador inicial para Keycloak + +Crea un archivo de propiedades: + +```bash +sudo nano /opt/keycloak/conf/keycloak.conf +``` + +Agrega estas líneas: + +``` +# Configuración básica +http-port=8080 +https-port=8443 +hostname=localhost + +# Configuración de administrador inicial +http-enabled=true +``` + +### Iniciar Keycloak en modo desarrollo + +```bash +cd /opt/keycloak +export KEYCLOAK_ADMIN=admin +export KEYCLOAK_ADMIN_PASSWORD=admin +sudo -u keycloak bin/kc.sh start-dev +``` + +Esto iniciará Keycloak con un usuario administrador "admin" y contraseña "admin". + +### Configurar Keycloak como servicio + +En otra terminal, crea un archivo de servicio systemd: + +```bash +sudo nano /etc/systemd/system/keycloak.service +``` + +Con el siguiente contenido: + +```ini +[Unit] +Description=Keycloak Application Server +After=network.target + +[Service] +Type=idle +User=keycloak +Group=keycloak +Environment="KEYCLOAK_ADMIN=admin" +Environment="KEYCLOAK_ADMIN_PASSWORD=admin" +ExecStart=/opt/keycloak/bin/kc.sh start-dev +TimeoutStartSec=600 +TimeoutStopSec=600 + +[Install] +WantedBy=multi-user.target +``` + +Habilita e inicia el servicio: + +```bash +sudo systemctl daemon-reload +sudo systemctl enable keycloak +sudo systemctl start keycloak +``` + +## 5. Configuración de Keycloak en la interfaz web + +Ahora puedes acceder a la consola de administración de Keycloak en http://localhost:8080/admin/ e iniciar sesión con: + +- Usuario: `admin` +- Contraseña: `admin` + +### Crear un nuevo Reino (Realm) + +1. Haz clic en el menú desplegable superior izquierdo que dice "master" +2. Selecciona "Create Realm" +3. Ingresa el nombre: `angular-app` +4. Haz clic en "Create" + +### Configurar la Federación de Usuarios LDAP + +1. En el menú lateral izquierdo, selecciona "User Federation" + +2. Haz clic en "Add provider" → "ldap" + +3. Completa los siguientes campos: + + - Console Display Name: `LDAP` + - Vendor: `Other` + - Connection URL: `ldap://localhost:389` + - Enable StartTLS: `OFF` + - Bind Type: `simple` + - Bind DN: `cn=admin,dc=correos,dc=com` + - Bind Credential: (la contraseña de admin LDAP) + - Edit Mode: `WRITABLE` + - Users DN: `ou=usuarios,dc=correos,dc=com` + - Username LDAP attribute: `uid` + - RDN LDAP attribute: `uid` + - UUID LDAP attribute: `entryUUID` + - User Object Classes: `inetOrgPerson, posixAccount` + - Custom User LDAP Filter: (dejar en blanco) + - Search Scope: `One Level` +4. Haz clic en "Test connection" y "Test authentication" para verificar + +5. Guarda la configuración + +6. En la pantalla del proveedor LDAP, ve a la pestaña "Synchronization" + +7. Haz clic en "Sync all users" + + +### Configurar el Mapeo de Grupos LDAP + +1. En la pantalla del proveedor LDAP, ve a la pestaña "Mappers" +2. Haz clic en "Create" +3. Completa: + - Name: `group-mapper` + - Mapper Type: `group-ldap-mapper` + - LDAP Groups DN: `ou=grupos,dc=correos,dc=com` + - Group Object Classes: `posixGroup` + - Membership LDAP Attribute: `memberUid` + - Group Name LDAP Attribute: `cn` + - User Roles Retrieve Strategy: `LOAD_GROUPS_BY_MEMBER_ATTRIBUTE` + - Member-Of LDAP Attribute: `memberOf` + - Mapped Group Attributes: (dejar en blanco) + - Drop non-existing groups during sync: `ON` +4. Haz clic en "Save" +5. En la pantalla del mapper, haz clic en "Sync LDAP Groups to Keycloak" + +### Crear un Cliente para Angular + +1. En el menú lateral izquierdo, selecciona "Clients" + +2. Haz clic en "Create client" + +3. Completa: + + - Client type: `OpenID Connect` + - Client ID: `angular-app` + - Name: `Angular Application` + - Haz clic en "Next" +4. En la siguiente pantalla (para aplicaciones SPA modernas): + + - Client authentication: `OFF` (para aplicaciones SPA) + - Authorization: `OFF` + - Haz clic en "Next" +5. En la siguiente pantalla: + + - Root URL: `http://localhost:4200` + - Home URL: `/` + - Valid redirect URIs: `http://localhost:4200/*` + - Web origins: `http://localhost:4200` (o usar `+` para permitir todos los orígenes durante desarrollo) + - Haz clic en "Save" + +Para aplicaciones que no son SPA, puedes habilitar "Client authentication" y obtener un client secret que deberás usar en la configuración. + +## 6. Integración con Angular: Enfoque básico + +Para las versiones más antiguas de Angular, vamos a utilizar el enfoque tradicional de integración con Keycloak. + +### Crear una aplicación Angular + +```bash +# Crear una nueva aplicación +ng new angular-keycloak-app +cd angular-keycloak-app + +# Instalar la biblioteca para integrar Keycloak +npm install keycloak-angular keycloak-js +``` + +### Configurar Keycloak en Angular + +Crea un archivo de configuración en `src/assets/keycloak.json`: + +```json +{ + "realm": "angular-app", + "auth-server-url": "http://localhost:8080/", + "resource": "angular-app", + "public-client": true +} +``` + +Si tu cliente en Keycloak tiene autenticación habilitada, deberás agregar el campo credentials con el client secret: + +```json +{ + "realm": "angular-app", + "auth-server-url": "http://localhost:8080/", + "resource": "angular-app", + "credentials": { + "secret": "TU_CLIENT_SECRET_AQUÍ" + } +} +``` + +Crea un archivo `src/assets/silent-check-sso.html`: + +```html + + + + + +``` + +### Configurar el módulo principal (Angular tradicional) + +Modifica el archivo `src/app/app.module.ts`: + +```typescript +import { NgModule, APP_INITIALIZER } from '@angular/core'; +import { BrowserModule } from '@angular/platform-browser'; +import { KeycloakAngularModule, KeycloakService } from 'keycloak-angular'; +import { AppRoutingModule } from './app-routing.module'; +import { AppComponent } from './app.component'; + +function initializeKeycloak(keycloak: KeycloakService) { + return () => + keycloak.init({ + config: { + url: 'http://localhost:8080', + realm: 'angular-app', + clientId: 'angular-app' + }, + initOptions: { + onLoad: 'check-sso', + silentCheckSsoRedirectUri: + window.location.origin + '/assets/silent-check-sso.html' + } + }); +} + +@NgModule({ + declarations: [ + AppComponent + ], + imports: [ + BrowserModule, + AppRoutingModule, + KeycloakAngularModule + ], + providers: [ + { + provide: APP_INITIALIZER, + useFactory: initializeKeycloak, + multi: true, + deps: [KeycloakService] + } + ], + bootstrap: [AppComponent] +}) +export class AppModule { } +``` + +### Crear un servicio de autenticación básico + +```bash +ng generate service auth +``` + +Edita `src/app/auth.service.ts`: + +```typescript +import { Injectable } from '@angular/core'; +import { KeycloakService } from 'keycloak-angular'; +import { KeycloakProfile } from 'keycloak-js'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + + constructor(private keycloak: KeycloakService) { } + + public getLoggedUser(): Promise { + return this.keycloak.loadUserProfile(); + } + + public login(): void { + this.keycloak.login(); + } + + public logout(): void { + this.keycloak.logout(); + } + + public isLoggedIn(): Promise { + return this.keycloak.isLoggedIn(); + } + + public getRoles(): string[] { + return this.keycloak.getUserRoles(); + } +} +``` + +### Crear un guardia para rutas protegidas (Angular tradicional) + +```bash +ng generate guard auth +``` + +Edita `src/app/auth.guard.ts`: + +```typescript +import { Injectable } from '@angular/core'; +import { ActivatedRouteSnapshot, RouterStateSnapshot, Router, UrlTree } from '@angular/router'; +import { KeycloakAuthGuard, KeycloakService } from 'keycloak-angular'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthGuard extends KeycloakAuthGuard { + + constructor( + protected override readonly router: Router, + protected readonly keycloak: KeycloakService + ) { + super(router, keycloak); + } + + public async isAccessAllowed( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot + ): Promise { + + // Verifica si el usuario está autenticado + if (!this.authenticated) { + await this.keycloak.login({ + redirectUri: window.location.origin + state.url, + }); + return false; + } + + // Obtiene los roles requeridos desde la ruta + const requiredRoles = route.data['roles']; + + // Permite el acceso si no hay roles requeridos + if (!requiredRoles || requiredRoles.length === 0) { + return true; + } + + // Verifica si el usuario tiene al menos uno de los roles requeridos + return requiredRoles.some((role: string) => this.roles.includes(role)); + } +} +``` + +### Configurar las rutas con protección + +Modifica `src/app/app-routing.module.ts`: + +```typescript +import { NgModule } from '@angular/core'; +import { RouterModule, Routes } from '@angular/router'; +import { AuthGuard } from './auth.guard'; +import { AppComponent } from './app.component'; + +const routes: Routes = [ + { + path: 'admin', + component: AppComponent, + canActivate: [AuthGuard], + data: { roles: ['administradores'] } + }, + { + path: 'developer', + component: AppComponent, + canActivate: [AuthGuard], + data: { roles: ['desarrolladores'] } + }, + { + path: 'user', + component: AppComponent, + canActivate: [AuthGuard], + data: { roles: ['usuarios'] } + }, + { + path: '', + component: AppComponent + } +]; + +@NgModule({ + imports: [RouterModule.forRoot(routes)], + exports: [RouterModule] +}) +export class AppRoutingModule { } +``` + +## 7. Integración con Angular 19: Enfoque moderno + +Para Angular 19 y versiones más recientes, utilizaremos el enfoque moderno que aprovecha características como componentes independientes y el sistema de inyección de dependencias mejorado. + +### Configuración en app.config.ts (Angular 19) + +Configura Keycloak en el archivo `src/app/app.config.ts`: + +```typescript +import { ApplicationConfig, provideZoneChangeDetection } from '@angular/core'; +import { provideRouter, withPreloading, PreloadAllModules } from '@angular/router'; +import { provideHttpClient, withFetch, withInterceptors } from '@angular/common/http'; +import { routes } from './app.routes'; +import { provideAnimations } from '@angular/platform-browser/animations'; +import { + provideKeycloak, + createInterceptorCondition, + IncludeBearerTokenCondition, + INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + includeBearerTokenInterceptor +} from 'keycloak-angular'; + +// Define condiciones para incluir el token en las peticiones +const localhostCondition = createInterceptorCondition({ + urlPattern: /^(http:\/\/localhost)(\/.*)?$/i, // URLs que comienzan con http://localhost + bearerPrefix: 'Bearer' +}); + +// Condición para APIs +const apiCondition = createInterceptorCondition({ + urlPattern: /^(\/api)(\/.*)?$/i, // URLs que comienzan con /api + bearerPrefix: 'Bearer' +}); + +export const appConfig: ApplicationConfig = { + providers: [ + provideZoneChangeDetection({ eventCoalescing: true }), + provideRouter( + routes, + withPreloading(PreloadAllModules) + ), + provideAnimations(), + // Usar el interceptor de Keycloak para adjuntar tokens de autenticación + provideHttpClient( + withFetch(), + withInterceptors([includeBearerTokenInterceptor]) + ), + // Configuración para Keycloak + provideKeycloak({ + config: { + url: 'http://localhost:8080', // URL del servidor Keycloak + realm: 'angular-app', // Nombre del realm + clientId: 'angular-app' // ID del cliente + }, + initOptions: { + onLoad: 'check-sso', // Opciones: 'login-required' o 'check-sso' + silentCheckSsoRedirectUri: `${window.location.origin}/silent-check-sso.html`, + checkLoginIframe: false, + pkceMethod: 'S256' // Mejora la seguridad + }, + // Configurar el interceptor especificando las URLs donde incluir el token + providers: [ + { + provide: INCLUDE_BEARER_TOKEN_INTERCEPTOR_CONFIG, + useValue: [localhostCondition, apiCondition] + } + ] + }) + ] +}; +``` + +## 8. Servicios de autenticación avanzados + +Con Angular 19, podemos crear un servicio de autenticación más avanzado utilizando signals y effects. + +```typescript +import { Injectable, inject, effect, signal } from '@angular/core'; +import { HttpClient } from '@angular/common/http'; +import { BehaviorSubject, Observable, from } from 'rxjs'; +import { KEYCLOAK_EVENT_SIGNAL, KeycloakEventType } from 'keycloak-angular'; +import { Router } from '@angular/router'; +import Keycloak from 'keycloak-js'; + +@Injectable({ + providedIn: 'root' +}) +export class AuthService { + // Inyectar Keycloak directamente + private keycloak = inject(Keycloak); + private keycloakEvents = inject(KEYCLOAK_EVENT_SIGNAL); + private router = inject(Router); + + // Estado del usuario + private userSubject = new BehaviorSubject(null); + public user$ = this.userSubject.asObservable(); + + // Estado de autenticación como signal + public isAuthenticated = signal(false); + + constructor(private http: HttpClient) { + // Verificar estado inicial + this.checkInitialAuthState(); + + // Configurar manejadores de eventos usando Angular effects + effect(() => { + const event = this.keycloakEvents(); + if (!event) return; + + console.log('Keycloak event:', event.type); + + // Autenticación exitosa + if (event.type === KeycloakEventType.AuthSuccess) { + this.isAuthenticated.set(true); + this.loadUserInfo(); + } + + // Cierre de sesión + if (event.type === KeycloakEventType.AuthLogout) { + this.isAuthenticated.set(false); + this.userSubject.next(null); + this.router.navigate(['/login']); + } + + // Error de autenticación + if (event.type === KeycloakEventType.AuthError) { + console.error('Authentication error:', event); + this.isAuthenticated.set(false); + this.userSubject.next(null); + } + + // Expiración del token + if (event.type === KeycloakEventType.TokenExpired) { + console.log('Token expired, refreshing...'); + this.updateToken(); + } + }); + } + + private async checkInitialAuthState(): Promise { + try { + const isLoggedIn = await this.keycloak.authenticated; + this.isAuthenticated.set(isLoggedIn); + + if (isLoggedIn) { + await this.loadUserInfo(); + } + } catch (error) { + console.error('Error checking initial auth state:', error); + this.isAuthenticated.set(false); + } + } + + private async loadUserInfo(): Promise { + try { + const isLoggedIn = await this.keycloak.authenticated; + + if (!isLoggedIn) { + this.userSubject.next(null); + return; + } + + const userProfile = await this.keycloak.loadUserProfile(); + const isAdmin = this.keycloak.hasRealmRole('admin'); + + // Obtener roles del usuario + const realmRoles = this.keycloak.realmAccess?.roles || []; + const resourceRoles = this.keycloak.resourceAccess || {}; + + const user = { + id: userProfile.id, + username: userProfile.username, + name: `${userProfile.firstName || ''} ${userProfile.lastName || ''}`.trim(), + email: userProfile.email, + role: isAdmin ? 'admin' : 'user', + roles: { + realm: realmRoles, + resource: resourceRoles + }, + isAdmin: isAdmin + }; + + this.userSubject.next(user); + } catch (error) { + console.error('Error loading user profile:', error); + this.userSubject.next(null); + } + } + + login(redirectUri?: string): Promise { + return this.keycloak.login({ + redirectUri: redirectUri || window.location.origin + }); + } + + logout(): Promise { + return this.keycloak.logout({ + redirectUri: window.location.origin + }); + } + + isLoggedIn(): Observable { + try { + return from(Promise.resolve(this.keycloak.authenticated || false)); + } catch (error) { + console.error('Error checking authentication:', error); + return from(Promise.resolve(false)); + } + } + + getToken(): Promise { + try { + return Promise.resolve(this.keycloak.token || ''); + } catch (error) { + console.error('Error getting token:', error); + return Promise.resolve(''); + } + } + + async updateToken(minValidity = 30): Promise { + try { + return await this.keycloak.updateToken(minValidity); + } catch (error) { + console.error('Error refreshing token:', error); + await this.login(); + return false; + } + } + + getCurrentUser(): any { + return this.userSubject.value; + } + + // Verificar si el usuario tiene un rol específico + hasRole(role: string): boolean { + return this.keycloak.hasRealmRole(role); + } + + // Verificar si el usuario tiene alguno de los roles especificados + hasAnyRole(roles: string[]): boolean { + for (const role of roles) { + if (this.keycloak.hasRealmRole(role)) { + return true; + } + } + return false; + } +} +``` + +## 9. Guardias de ruta e interceptores HTTP + +### Guardia de ruta funcional (Angular 19) + +```typescript +import { inject } from '@angular/core'; +import { CanActivateFn, Router } from '@angular/router'; +import Keycloak from 'keycloak-js'; +import { ActivatedRouteSnapshot, RouterStateSnapshot, UrlTree } from '@angular/router'; +import { createAuthGuard, AuthGuardData } from 'keycloak-angular'; + +// Implementación directa +export const authGuard: CanActivateFn = async ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot +): Promise => { + const keycloak = inject(Keycloak); + const router = inject(Router); + + try { + // Verificar si el usuario está autenticado + const authenticated = await keycloak.authenticated; + + if (authenticated) { + // Verificar roles si están especificados en la ruta + const requiredRoles = route.data['roles'] as string[]; + + if (requiredRoles && requiredRoles.length > 0) { + // Comprobar si el usuario tiene los roles requeridos + const hasRequiredRole = requiredRoles.some(role => + keycloak.hasRealmRole(role) + ); + + if (!hasRequiredRole) { + console.log('Usuario no tiene los roles requeridos'); + return router.createUrlTree(['/unauthorized']); + } + } + + return true; + } + + // Si no está autenticado, redirigir a la página de login + console.log('Usuario no autenticado, redirigiendo a login'); + return router.createUrlTree(['/login'], { + queryParams: { returnUrl: state.url !== '/' ? state.url : '/inicio' } + }); + } catch (error) { + console.error('Error al verificar autenticación:', error); + return router.createUrlTree(['/login']); + } +}; + +// Alternativa usando el helper de keycloak-angular +const isAccessAllowed = async ( + route: ActivatedRouteSnapshot, + state: RouterStateSnapshot, + authData: AuthGuardData +): Promise => { + const { authenticated, grantedRoles } = authData; + const router = inject(Router); + + if (authenticated) { + const requiredRoles = route.data['roles'] as string[]; + + if (!requiredRoles || requiredRoles.length === 0) { + return true; + } + + const hasRequiredRole = requiredRoles.some(role => + grantedRoles.realmRoles.includes(role) + ); + + if (hasRequiredRole) { + return true; + } + + return router.createUrlTree(['/unauthorized']); + } + + return router.createUrlTree(['/login'], { queryParams: { returnUrl: state.url } }); +}; + +// Helper para crear el guardia (opcional) +export const authGuardWithHelper = createAuthGuard(isAccessAllowed); +``` + +### Interceptor HTTP para manejo de errores + +```typescript +import { inject } from '@angular/core'; +import { + HttpRequest, + HttpHandlerFn, + HttpInterceptorFn, + HttpErrorResponse +} from '@angular/common/http'; +import { Observable, throwError, from, switchMap } from 'rxjs'; +import { catchError } from 'rxjs/operators'; +import Keycloak from 'keycloak-js'; +import { Router } from '@angular/router'; + +/** + * Este interceptor complementa al incluido en app.config.ts + * Proporciona funcionalidad adicional para manejo de errores + */ +export const authInterceptor: HttpInterceptorFn = ( + request: HttpRequest, + next: HttpHandlerFn +): Observable => { + const keycloak = inject(Keycloak); + const router = inject(Router); + + // Manejar la petición con gestión de errores de autenticación + return next(request).pipe( + catchError((error: HttpErrorResponse) => { + // Manejar errores 401 Unauthorized + if (error.status === 401) { + console.log('Error 401, refrescando token o redirigiendo a login'); + + // Intentar refrescar el token primero + return from(keycloak.updateToken(30)).pipe( + switchMap(refreshed => { + if (refreshed) { + // Token refrescado, reintentar la petición + return next(request); + } else { + // No se pudo refrescar el token, redirigir a login + keycloak.login(); + return throwError(() => error); + } + }), + catchError(refreshError => { + console.error('Error al refrescar token:', refreshError); + // Redirigir a login en caso de error + router.navigate(['/login']); + return throwError(() => error); + }) + ); + } + + // Para otros errores, simplemente pasarlos + return throwError(() => error); + }) + ); +}; +``` + +## 10. Componentes de UI para login/logout + +### Componente de login (Angular 19) + +```typescript +import { Component, OnInit, inject } from '@angular/core'; +import { CommonModule } from '@angular/common'; +import { Router, ActivatedRoute } from '@angular/router'; +import { AuthService } from '../../services/auth.service'; +import { take } from 'rxjs/operators'; + +@Component({ + selector: 'app-login', + standalone: true, + imports: [CommonModule], + templateUrl: './login.component.html', + styleUrl: './login.component.scss' +}) +export class LoginComponent implements OnInit { + private authService = inject(AuthService); + private route = inject(ActivatedRoute); + private router = inject(Router); + + loading: boolean = false; + returnUrl: string = ''; + + ngOnInit() { + // Obtener el returnUrl de los query params + this.returnUrl = this.route.snapshot.queryParams['returnUrl'] || '/inicio'; + + // Verificar si ya está autenticado y redirigir + this.authService.isLoggedIn().subscribe({ + next: (isLoggedIn) => { + if (isLoggedIn) { + console.log('Usuario ya autenticado, redirigiendo a:', this.returnUrl); + // Comprobar si la URL de retorno es válida + const effectiveReturnUrl = this.returnUrl === '/' ? '/inicio' : this.returnUrl; + // Redirigir + this.router.navigate([effectiveReturnUrl], { replaceUrl: true }); + } + }, + error: (error) => console.error('Error al verificar autenticación:', error) + }); + } + + async onLogin() { + this.loading = true; + + try { + // Comprobar si la URL de retorno es válida + const effectiveReturnUrl = this.returnUrl === '/' ? '/inicio' : this.returnUrl; + + // Construir la redirectUri + const redirectUri = window.location.origin + effectiveReturnUrl; + console.log('Iniciando login con redirectUri:', redirectUri); + + // Iniciar el flujo de autenticación + await this.authService.login(redirectUri); + // Keycloak se encargará de la redirección + } catch (error) { + console.error('Error al iniciar sesión:', error); + this.loading = false; + } + } +} +``` + +### Plantilla HTML para el componente de login + +```html + +``` + +### Componente principal con información de usuario (común para ambos enfoques) + +Edita `src/app/app.component.ts`: + +```typescript +import { Component, OnInit } from '@angular/core'; +import { AuthService } from './auth.service'; +import { KeycloakProfile } from 'keycloak-js'; + +@Component({ + selector: 'app-root', + templateUrl: './app.component.html', + styleUrls: ['./app.component.css'] +}) +export class AppComponent implements OnInit { + title = 'angular-keycloak-app'; + isLoggedIn = false; + userProfile: KeycloakProfile | null = null; + userRoles: string[] = []; + + constructor(private authService: AuthService) {} + + async ngOnInit() { + this.isLoggedIn = await this.authService.isLoggedIn(); + + if (this.isLoggedIn) { + this.userProfile = await this.authService.getLoggedUser(); + this.userRoles = this.authService.getRoles(); + } + } + + login() { + this.authService.login(); + } + + logout() { + this.authService.logout(); + } +} +``` + +Edita `src/app/app.component.html`: + +```html +
+
+
+
+
+

Demo Keycloak con LDAP - Correos.com

+
+
+
+

Bienvenido, {{ userProfile?.firstName }} {{ userProfile?.lastName }}

+

Email: {{ userProfile?.email }}

+

Nombre de Usuario: {{ userProfile?.username }}

+ +

Tus Roles:

+
    +
  • {{ role }}
  • +
+ + +
+ + +

Por favor inicia sesión para acceder al sistema

+ +
+
+
+
+
+
+``` + +## 11. Arquitectura del sistema + +La arquitectura del sistema está compuesta por los siguientes componentes: + +- **OpenLDAP**: Directorio de usuarios y grupos (puerto 389) +- **phpLDAPadmin**: Interfaz gráfica para administrar LDAP (puerto 80) +- **Keycloak**: Servidor de autenticación y autorización (puerto 8080) +- **Aplicación Angular**: Cliente que utiliza el SSO de Keycloak (puerto 4200) + +La estructura del directorio LDAP para correos.com es: + +- dc=correos,dc=com (raíz del directorio) + - ou=usuarios (unidad organizativa para usuarios) + - uid=admin (usuario administrador) + - uid=developer (usuario desarrollador) + - uid=user (usuario normal) + - ou=grupos (unidad organizativa para grupos) + - cn=administradores (grupo de administradores) + - cn=desarrolladores (grupo de desarrolladores) + - cn=usuarios (grupo de usuarios) + +### Flujo de autenticación + +El flujo de autenticación funciona de la siguiente manera: + +1. El usuario accede a la aplicación Angular +2. Si el usuario no ha iniciado sesión, es redirigido a Keycloak +3. Keycloak autentica al usuario contra LDAP +4. Si la autenticación es exitosa, Keycloak redirige al usuario de vuelta a la aplicación con un token JWT +5. La aplicación Angular verifica el token y permite el acceso +6. Los interceptores HTTP añaden automáticamente el token a las peticiones API +7. Las rutas protegidas verifican los roles del usuario antes de permitir el acceso + +## 12. Resolución de problemas comunes + +### Problema: "ldap_bind: Invalid credentials (49)" + +Este error ocurre cuando intentas conectarte a LDAP con credenciales incorrectas. Para resolverlo: + +1. Verifica que estás usando el dominio correcto: `dc=correos,dc=com` +2. Asegúrate de usar el DN correcto: `cn=admin,dc=correos,dc=com` +3. Comprueba que la contraseña de administrador es correcta + +Si olvidaste la contraseña, puedes restablecerla: + +```bash +sudo dpkg-reconfigure slapd +``` + +O también: + +```bash +sudo slappasswd +# Copia el hash generado +sudo nano /etc/ldap/slapd.d/cn=config/olcDatabase={1}mdb.ldif +# Busca olcRootPW y reemplaza el valor con el nuevo hash +sudo systemctl restart slapd +``` + +### Problema: No puedes conectarte a LDAP en absoluto + +```bash +# Verifica que el servicio esté ejecutándose +sudo systemctl status slapd + +# Reinicia el servicio si es necesario +sudo systemctl restart slapd + +# Verifica que el puerto esté abierto +sudo netstat -tulpn | grep 389 +``` + +### Problema: Keycloak no se inicia + +```bash +# Verifica los logs +sudo journalctl -u keycloak + +# Asegúrate de que Java esté instalado correctamente +java -version + +# Verifica los permisos +sudo ls -la /opt/keycloak +``` + +### Problema: Error 401 Unauthorized al autenticar con Keycloak + +Este error suele ocurrir cuando hay problemas con la configuración del cliente en Keycloak: + +1. Verifica que el realm y el clientId sean correctos en la configuración de Angular +2. Asegúrate de que el cliente en Keycloak tenga la configuración correcta: + - Web Origins: debe incluir `http://localhost:4200` o `+` para desarrollo + - Valid redirect URIs: debe incluir `http://localhost:4200/*` + - Client authentication debe estar OFF para aplicaciones SPA + - Access Type debe ser "public" + +### Problema: Redireccionamiento circular + +Si ves redirecciones constantes entre tu aplicación y Keycloak: + +1. Verifica la lógica en el componente app.component.ts y login.component.ts +2. Asegúrate de que no haya múltiples redirecciones activándose a la vez +3. Usa el parámetro `replaceUrl: true` en las navegaciones para evitar acumular entradas en el historial +4. Maneja correctamente las rutas especiales como '/' redirigiendo a una ruta válida como '/inicio' + +### Problema: "Can't resolve 'keycloak-angular'" + +Si encuentras este error al compilar: + +1. Asegúrate de haber instalado correctamente las dependencias: + ```bash + npm install keycloak-angular keycloak-js + ``` +2. Verifica que las versiones sean compatibles con tu versión de Angular +3. Limpia la caché de npm y reinstala: + ```bash + npm cache clean --force + rm -rf node_modules + npm install + ``` + +### Problema: El token no se adjunta a las peticiones HTTP + +1. Verifica que estés utilizando el interceptor correcto +2. Asegúrate de que las condiciones de URL para el token sean correctas +3. Revisa si estás usando `provideHttpClient` con `withInterceptors([includeBearerTokenInterceptor])` +4. Comprueba los patrones en `createInterceptorCondition` para asegurarte de que coincidan con tus URLs + +## 13. Verificación y prueba del sistema + +### Verificar que Keycloak esté sincronizando correctamente con LDAP + +1. Inicia sesión en la consola de administración de Keycloak (http://localhost:8080/admin/) +2. Navega a "Users" en el reino "angular-app" +3. Deberías ver los usuarios de LDAP: admin, developer y user +4. Navega a "Groups" y verifica que los grupos de LDAP estén presentes + +### Probar la aplicación Angular + +1. Asegúrate de que Keycloak esté ejecutándose +2. Inicia la aplicación Angular con `ng serve` +3. Navega a http://localhost:4200 +4. Haz clic en "Iniciar Sesión" +5. Deberías ser redirigido a la pantalla de inicio de sesión de Keycloak +6. Inicia sesión con uno de los usuarios LDAP: + - Usuario: admin, Contraseña: password123 + - Usuario: developer, Contraseña: password123 + - Usuario: user, Contraseña: password123 +7. Después de iniciar sesión, serás redirigido de vuelta a la aplicación +8. La aplicación mostrará tu perfil y roles + +## 14. Recursos adicionales + +- [Documentación oficial de Keycloak](https://www.keycloak.org/documentation) +- [Documentación oficial de OpenLDAP](https://www.openldap.org/doc/) +- [Documentación oficial de keycloak-angular](https://github.com/mauriciovigolo/keycloak-angular) +- [Guía de migración para Keycloak-Angular v19](https://www.keycloak.org/securing-apps/v19.0.2/angular) +- [Ejemplos de implementación en GitHub](https://github.com/mauriciovigolo/keycloak-angular/tree/main/examples/standalone-app) + +## 15. Resumen + +Esta configuración proporciona un sistema completo de gestión de identidades y acceso con: + +- **Autenticación centralizada**: Los usuarios solo necesitan recordar un conjunto de credenciales +- **Gestión de usuarios unificada**: Todos los usuarios se administran en LDAP +- **Control de acceso basado en roles**: Las rutas y funcionalidades pueden ser protegidas según los roles del usuario +- **Experiencia de inicio de sesión único (SSO)**: Una vez autenticado, el usuario puede acceder a todas las aplicaciones integradas +- **Soporte para versiones modernas de Angular**: Incluye configuraciones para Angular tradicional y Angular 19 con standalone components y signals + +Este sistema es adecuado para entornos empresariales donde se requiere un control de acceso detallado y una gestión centralizada de usuarios. + +La implementación es nativa en un sistema Ubuntu, lo que la hace ideal para despliegues en servidores VPS o entornos similares sin necesidad de contenedores. También funciona perfectamente en entornos de desarrollo local. \ No newline at end of file