Soluciona un problema por el que los miembros del grupo no se mostraban en Keycloak después de la sincronización LDAP. Explica la importancia de configurar «Membership Attribute Type» a «UID» en lugar de «DN» en la configuración del mapeador de grupos LDAP. Esto asegura correcta recuperación de miembros basada en el simple ID de usuario almacenado en la estructura LDAP. de LDAP. Explains the importance of setting "Membership Attribute Type" to "UID" instead of "DN" in the LDAP group mapper configuration. This ensures correct member retrieval based on the simple user ID stored in the LDAP structure.
1492 lines
42 KiB
Markdown
1492 lines
42 KiB
Markdown
# 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`
|
|
- **Membership Attribute Type: `UID`** (¡IMPORTANTE! Debe ser UID, no DN)
|
|
- Membership User LDAP Attribute: `uid`
|
|
- 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"
|
|
|
|
> **Nota importante sobre el Membership Attribute Type:**
|
|
> Es crucial seleccionar `UID` y no `DN` porque en nuestra estructura LDAP, los valores de `memberUid` contienen solo el uid simple del usuario (admin, developer, user) y no el DN completo. Si configuras este campo incorrectamente como `DN`, aparecerá un error al intentar ver los miembros de cualquier grupo.
|
|
|
|
### 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
|
|
<html>
|
|
<body>
|
|
<script>
|
|
parent.postMessage(location.href, location.origin);
|
|
</script>
|
|
</body>
|
|
</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<KeycloakProfile | null> {
|
|
return this.keycloak.loadUserProfile();
|
|
}
|
|
|
|
public login(): void {
|
|
this.keycloak.login();
|
|
}
|
|
|
|
public logout(): void {
|
|
this.keycloak.logout();
|
|
}
|
|
|
|
public isLoggedIn(): Promise<boolean> {
|
|
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<boolean | UrlTree> {
|
|
|
|
// 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<IncludeBearerTokenCondition>({
|
|
urlPattern: /^(http:\/\/localhost)(\/.*)?$/i, // URLs que comienzan con http://localhost
|
|
bearerPrefix: 'Bearer'
|
|
});
|
|
|
|
// Condición para APIs
|
|
const apiCondition = createInterceptorCondition<IncludeBearerTokenCondition>({
|
|
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<any>(null);
|
|
public user$ = this.userSubject.asObservable();
|
|
|
|
// Estado de autenticación como signal
|
|
public isAuthenticated = signal<boolean>(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<void> {
|
|
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<void> {
|
|
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<void> {
|
|
return this.keycloak.login({
|
|
redirectUri: redirectUri || window.location.origin
|
|
});
|
|
}
|
|
|
|
logout(): Promise<void> {
|
|
return this.keycloak.logout({
|
|
redirectUri: window.location.origin
|
|
});
|
|
}
|
|
|
|
isLoggedIn(): Observable<boolean> {
|
|
try {
|
|
return from(Promise.resolve(this.keycloak.authenticated || false));
|
|
} catch (error) {
|
|
console.error('Error checking authentication:', error);
|
|
return from(Promise.resolve(false));
|
|
}
|
|
}
|
|
|
|
getToken(): Promise<string> {
|
|
try {
|
|
return Promise.resolve(this.keycloak.token || '');
|
|
} catch (error) {
|
|
console.error('Error getting token:', error);
|
|
return Promise.resolve('');
|
|
}
|
|
}
|
|
|
|
async updateToken(minValidity = 30): Promise<boolean> {
|
|
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<boolean | UrlTree> => {
|
|
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<boolean | UrlTree> => {
|
|
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<unknown>,
|
|
next: HttpHandlerFn
|
|
): Observable<any> => {
|
|
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
|
|
<div class="login-container">
|
|
<div class="login-card">
|
|
<h2>Iniciar Sesión</h2>
|
|
<p>Por favor, inicia sesión para acceder al sistema.</p>
|
|
|
|
<button
|
|
[disabled]="loading"
|
|
(click)="onLogin()"
|
|
class="login-button">
|
|
{{ loading ? 'Cargando...' : 'Iniciar Sesión con Keycloak' }}
|
|
</button>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
### 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
|
|
<div class="container mt-5">
|
|
<div class="row">
|
|
<div class="col-md-12">
|
|
<div class="card">
|
|
<div class="card-header">
|
|
<h2>Demo Keycloak con LDAP - Correos.com</h2>
|
|
</div>
|
|
<div class="card-body">
|
|
<div *ngIf="isLoggedIn; else loginButton">
|
|
<h3>Bienvenido, {{ userProfile?.firstName }} {{ userProfile?.lastName }}</h3>
|
|
<p>Email: {{ userProfile?.email }}</p>
|
|
<p>Nombre de Usuario: {{ userProfile?.username }}</p>
|
|
|
|
<h4>Tus Roles:</h4>
|
|
<ul>
|
|
<li *ngFor="let role of userRoles">{{ role }}</li>
|
|
</ul>
|
|
|
|
<button class="btn btn-danger" (click)="logout()">Cerrar Sesión</button>
|
|
</div>
|
|
|
|
<ng-template #loginButton>
|
|
<p>Por favor inicia sesión para acceder al sistema</p>
|
|
<button class="btn btn-primary" (click)="login()">Iniciar Sesión</button>
|
|
</ng-template>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
```
|
|
|
|
## 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
|
|
|
|
### Problema: No puedes ver los miembros de un grupo en Keycloak
|
|
|
|
Este error ocurre cuando hay una configuración incorrecta en el mapeo de grupos LDAP:
|
|
|
|
1. **Causa principal**: El tipo de atributo de membresía (Membership Attribute Type) está configurado como `DN` cuando debería ser `UID`.
|
|
2. **Solución**: En User Federation → LDAP → group-mapper → Edit, cambia "Membership Attribute Type" de `DN` a `UID`.
|
|
3. **Explicación**: En la estructura LDAP creada, los valores de `memberUid` contienen sólo el uid simple (admin, developer, user) y no el DN completo.
|
|
4. Después de hacer el cambio, vuelve a ejecutar "Sync LDAP Groups to Keycloak".
|
|
|
|
## 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
|
|
5. Haz clic en cada grupo y verifica que los miembros aparezcan correctamente
|
|
- Si no aparecen, revisa la sección "Resolución de problemas" sobre el tipo de atributo de membresía
|
|
|
|
### 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. |