1476 lines
40 KiB
Markdown
1476 lines
40 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`
|
|
- 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
|
|
<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
|
|
|
|
## 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. |