Mejora de la autenticación y la gestión de tokens
Mejora la lógica de actualización de tokens teniendo en cuenta la actividad del usuario y las configuraciones del entorno. Elimina el obsoleto AuthService e integra el registro de actividad para una mejor supervisión. Actualiza las configuraciones del entorno para gestionar los tiempos de inactividad y el comportamiento de renovación de tokens. Aborda los posibles problemas de renovación de tokens relacionados con los usuarios inactivos y mejora la seguridad aplicando políticas estrictas de renovación de tokens. Traducción realizada con la versión gratuita del traductor DeepL.com
This commit is contained in:
parent
8a1434e553
commit
f2ce7327d8
@ -40,6 +40,8 @@ function addToken(request: HttpRequest<unknown>, token: string): HttpRequest<unk
|
|||||||
|
|
||||||
// Función para manejar errores 401 (token expirado)
|
// Función para manejar errores 401 (token expirado)
|
||||||
function handle401Error(req: HttpRequest<unknown>, next: HttpHandlerFn, authService: DirectAuthService) {
|
function handle401Error(req: HttpRequest<unknown>, next: HttpHandlerFn, authService: DirectAuthService) {
|
||||||
|
// Intentar refrescar el token solo si el usuario está activo
|
||||||
|
// El método refreshToken() ya maneja el caso de inactividad internamente
|
||||||
return authService.refreshToken().pipe(
|
return authService.refreshToken().pipe(
|
||||||
switchMap((token: any) => {
|
switchMap((token: any) => {
|
||||||
return next(addToken(req, token.access_token));
|
return next(addToken(req, token.access_token));
|
||||||
|
|||||||
@ -1,69 +0,0 @@
|
|||||||
// This file is kept for reference only and is not used in the application.
|
|
||||||
// The application now uses DirectAuthService instead.
|
|
||||||
|
|
||||||
import { Injectable, signal } from '@angular/core';
|
|
||||||
import { HttpClient } from '@angular/common/http';
|
|
||||||
import { BehaviorSubject, Observable, from } from 'rxjs';
|
|
||||||
import { Router } from '@angular/router';
|
|
||||||
import { MessageService } from 'primeng/api';
|
|
||||||
import { environment } from '../../environments/environment';
|
|
||||||
|
|
||||||
@Injectable({
|
|
||||||
providedIn: 'root'
|
|
||||||
})
|
|
||||||
export class AuthService {
|
|
||||||
// User state
|
|
||||||
private userSubject = new BehaviorSubject<any>(null);
|
|
||||||
public user$ = this.userSubject.asObservable();
|
|
||||||
|
|
||||||
// Authentication state as a signal
|
|
||||||
public isAuthenticated = signal<boolean>(false);
|
|
||||||
|
|
||||||
constructor(
|
|
||||||
private http: HttpClient,
|
|
||||||
private router: Router,
|
|
||||||
private messageService: MessageService
|
|
||||||
) {}
|
|
||||||
|
|
||||||
async login(redirectUri?: string): Promise<void> {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return Promise.reject('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
}
|
|
||||||
|
|
||||||
async logout(): Promise<void> {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return Promise.resolve();
|
|
||||||
}
|
|
||||||
|
|
||||||
isLoggedIn(): Observable<boolean> {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return from(Promise.resolve(false));
|
|
||||||
}
|
|
||||||
|
|
||||||
getToken(): Promise<string> {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return Promise.resolve('');
|
|
||||||
}
|
|
||||||
|
|
||||||
async updateToken(): Promise<boolean> {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
getCurrentUser(): any {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user has a specific role
|
|
||||||
hasRole(role: string): boolean {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Check if user has any of the specified roles
|
|
||||||
hasAnyRole(roles: string[]): boolean {
|
|
||||||
console.warn('AuthService is deprecated. Use DirectAuthService instead.');
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@ -32,16 +32,36 @@ export class DirectAuthService {
|
|||||||
// Idle detection
|
// Idle detection
|
||||||
private userActivity: any = null;
|
private userActivity: any = null;
|
||||||
private userInactive = signal<boolean>(false);
|
private userInactive = signal<boolean>(false);
|
||||||
|
private lastActivityTime: number = Date.now();
|
||||||
|
|
||||||
// Percentage for inactivity timeout (90% of token lifetime)
|
// Configuraciones de inactividad desde environment
|
||||||
private readonly INACTIVITY_PERCENTAGE = 0.9;
|
private readonly INACTIVITY_PERCENTAGE: number;
|
||||||
|
private readonly MIN_INACTIVITY_TIME: number;
|
||||||
|
private readonly STRICT_TOKEN_RENEWAL: boolean;
|
||||||
|
private readonly ENABLE_ACTIVITY_LOGS: boolean;
|
||||||
|
private readonly LOG_INTERVAL: number;
|
||||||
|
|
||||||
|
// Temporizador para logs de actividad
|
||||||
|
private activityLogTimer: any = null;
|
||||||
|
|
||||||
constructor(private http: HttpClient) {
|
constructor(private http: HttpClient) {
|
||||||
|
// Inicializar configuraciones desde environment
|
||||||
|
this.INACTIVITY_PERCENTAGE = environment.auth.inactivityPercentage;
|
||||||
|
this.MIN_INACTIVITY_TIME = environment.auth.minInactivityTime;
|
||||||
|
this.STRICT_TOKEN_RENEWAL = environment.auth.strictTokenRenewal;
|
||||||
|
this.ENABLE_ACTIVITY_LOGS = environment.auth.enableActivityLogs;
|
||||||
|
this.LOG_INTERVAL = environment.auth.logInterval;
|
||||||
|
|
||||||
// Intentar cargar el token del almacenamiento local al iniciar
|
// Intentar cargar el token del almacenamiento local al iniciar
|
||||||
this.loadTokenFromStorage();
|
this.loadTokenFromStorage();
|
||||||
|
|
||||||
// Iniciar monitoreo de actividad
|
// Iniciar monitoreo de actividad
|
||||||
this.setupActivityMonitoring();
|
this.setupActivityMonitoring();
|
||||||
|
|
||||||
|
// Iniciar logs de actividad si están habilitados
|
||||||
|
if (this.ENABLE_ACTIVITY_LOGS && this.LOG_INTERVAL > 0) {
|
||||||
|
this.startActivityLogging();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Cargar token del almacenamiento local
|
// Cargar token del almacenamiento local
|
||||||
@ -53,25 +73,47 @@ export class DirectAuthService {
|
|||||||
|
|
||||||
// Verificar si el token ha expirado
|
// Verificar si el token ha expirado
|
||||||
if (this.isTokenExpired()) {
|
if (this.isTokenExpired()) {
|
||||||
// Si tiene refresh token, intentar renovar
|
// Token expirado al cargar
|
||||||
if (this.tokenInfo.refresh_token) {
|
// Si tiene refresh token y (no está en modo estricto o el usuario ha estado activo), intentar renovar
|
||||||
|
if (this.tokenInfo.refresh_token && (!this.STRICT_TOKEN_RENEWAL || this.isUserActive())) {
|
||||||
|
// Usuario activo o no en modo estricto, intentando renovar token
|
||||||
this.refreshToken().subscribe();
|
this.refreshToken().subscribe();
|
||||||
} else {
|
} else {
|
||||||
|
// Usuario inactivo o sin refresh token, cerrando sesión
|
||||||
this.logout();
|
this.logout();
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Decodificar info del usuario desde el token
|
// Decodificar info del usuario desde el token
|
||||||
this.setUserFromToken(this.tokenInfo.access_token);
|
this.setUserFromToken(this.tokenInfo.access_token);
|
||||||
// Configurar temporizador para renovación de token
|
// Configurar temporizador para renovación de token
|
||||||
this.startRefreshTokenTimer();
|
// Solo si el usuario está activo (en modo estricto) o siempre (en modo no estricto)
|
||||||
|
if (!this.STRICT_TOKEN_RENEWAL || this.isUserActive()) {
|
||||||
|
this.startRefreshTokenTimer();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Iniciar logs de actividad si están habilitados
|
||||||
|
if (this.ENABLE_ACTIVITY_LOGS && this.LOG_INTERVAL > 0) {
|
||||||
|
console.log('[Auth] Iniciando logs al cargar token existente');
|
||||||
|
this.startActivityLogging();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error al cargar token:', e);
|
// Error al cargar token
|
||||||
this.logout();
|
this.logout();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Comprueba si el usuario ha estado activo dentro del período de inactividad
|
||||||
|
private isUserActive(): boolean {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const inactiveTime = currentTime - this.lastActivityTime;
|
||||||
|
|
||||||
|
// El usuario se considera activo si no ha estado inactivo por más tiempo que
|
||||||
|
// el porcentaje configurado del tiempo de vida del token o el tiempo mínimo
|
||||||
|
return inactiveTime < this.MIN_INACTIVITY_TIME;
|
||||||
|
}
|
||||||
|
|
||||||
// Login directo con credenciales
|
// Login directo con credenciales
|
||||||
public login(username: string, password: string): Observable<any> {
|
public login(username: string, password: string): Observable<any> {
|
||||||
const headers = new HttpHeaders({
|
const headers = new HttpHeaders({
|
||||||
@ -99,9 +141,18 @@ export class DirectAuthService {
|
|||||||
|
|
||||||
// Reiniciar detección de inactividad
|
// Reiniciar detección de inactividad
|
||||||
this.resetInactivity();
|
this.resetInactivity();
|
||||||
|
|
||||||
|
// Registrar tiempo de actividad
|
||||||
|
this.lastActivityTime = Date.now();
|
||||||
|
|
||||||
|
// Iniciar logs de actividad después del login si están habilitados
|
||||||
|
if (this.ENABLE_ACTIVITY_LOGS && this.LOG_INTERVAL > 0) {
|
||||||
|
console.log('[Auth] Iniciando logs después del login');
|
||||||
|
this.startActivityLogging();
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
console.error('Error de autenticación:', error);
|
// Error de autenticación
|
||||||
return throwError(() => new Error('Credenciales incorrectas o error de servidor'));
|
return throwError(() => new Error('Credenciales incorrectas o error de servidor'));
|
||||||
})
|
})
|
||||||
);
|
);
|
||||||
@ -115,6 +166,9 @@ export class DirectAuthService {
|
|||||||
// Detener monitoreo de actividad
|
// Detener monitoreo de actividad
|
||||||
this.stopActivityMonitoring();
|
this.stopActivityMonitoring();
|
||||||
|
|
||||||
|
// Detener logs de actividad
|
||||||
|
this.stopActivityLogging();
|
||||||
|
|
||||||
// Limpiar datos de sesión
|
// Limpiar datos de sesión
|
||||||
localStorage.removeItem('keycloak_token');
|
localStorage.removeItem('keycloak_token');
|
||||||
this.tokenInfo = null;
|
this.tokenInfo = null;
|
||||||
@ -124,15 +178,43 @@ export class DirectAuthService {
|
|||||||
this.router.navigate(['/login']);
|
this.router.navigate(['/login']);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Iniciar logs de actividad en intervalos regulares
|
||||||
|
private startActivityLogging(): void {
|
||||||
|
// Detener cualquier temporizador existente
|
||||||
|
this.stopActivityLogging();
|
||||||
|
|
||||||
|
// Crear nuevo temporizador para logs
|
||||||
|
this.activityLogTimer = setInterval(() => {
|
||||||
|
const currentTime = Date.now();
|
||||||
|
const inactiveTime = currentTime - this.lastActivityTime;
|
||||||
|
const inactiveSeconds = Math.round(inactiveTime / 1000);
|
||||||
|
|
||||||
|
// Calcular tiempo hasta expiración del token
|
||||||
|
const tokenExpirationTime = this.getTokenExpirationTime();
|
||||||
|
const expirationSeconds = Math.round(tokenExpirationTime / 1000);
|
||||||
|
|
||||||
|
console.log(`[Auth] Inactividad: ${inactiveSeconds}s | Expiración token: ${expirationSeconds}s | Inactivo: ${this.userInactive()} | Umbral: ${Math.round(this.MIN_INACTIVITY_TIME/1000)}s`);
|
||||||
|
}, this.LOG_INTERVAL);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Detener logs de actividad
|
||||||
|
private stopActivityLogging(): void {
|
||||||
|
if (this.activityLogTimer) {
|
||||||
|
clearInterval(this.activityLogTimer);
|
||||||
|
this.activityLogTimer = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Renovar token usando refresh token
|
// Renovar token usando refresh token
|
||||||
public refreshToken(): Observable<any> {
|
public refreshToken(): Observable<any> {
|
||||||
if (!this.tokenInfo?.refresh_token) {
|
if (!this.tokenInfo?.refresh_token) {
|
||||||
return throwError(() => new Error('No hay refresh token disponible'));
|
return throwError(() => new Error('No hay refresh token disponible'));
|
||||||
}
|
}
|
||||||
|
|
||||||
// No refrescar token si el usuario está inactivo
|
// Verificar si el usuario ha estado inactivo por más tiempo que el umbral configurado
|
||||||
if (this.userInactive()) {
|
// Si STRICT_TOKEN_RENEWAL está activado, verificar la actividad del usuario
|
||||||
console.log('Usuario inactivo, no se renovará el token');
|
if (this.STRICT_TOKEN_RENEWAL && !this.isUserActive()) {
|
||||||
|
// Usuario inactivo por más tiempo que el permitido, no se renovará el token
|
||||||
this.logout();
|
this.logout();
|
||||||
return throwError(() => new Error('Usuario inactivo'));
|
return throwError(() => new Error('Usuario inactivo'));
|
||||||
}
|
}
|
||||||
@ -146,9 +228,12 @@ export class DirectAuthService {
|
|||||||
.set('grant_type', 'refresh_token')
|
.set('grant_type', 'refresh_token')
|
||||||
.set('refresh_token', this.tokenInfo.refresh_token);
|
.set('refresh_token', this.tokenInfo.refresh_token);
|
||||||
|
|
||||||
|
// Intentando renovar el token
|
||||||
|
|
||||||
return this.http.post<any>(this.tokenEndpoint, params.toString(), { headers })
|
return this.http.post<any>(this.tokenEndpoint, params.toString(), { headers })
|
||||||
.pipe(
|
.pipe(
|
||||||
tap(newTokenInfo => {
|
tap(newTokenInfo => {
|
||||||
|
// Token renovado exitosamente
|
||||||
// Actualizar información del token
|
// Actualizar información del token
|
||||||
this.tokenInfo = newTokenInfo;
|
this.tokenInfo = newTokenInfo;
|
||||||
localStorage.setItem('keycloak_token', JSON.stringify(newTokenInfo));
|
localStorage.setItem('keycloak_token', JSON.stringify(newTokenInfo));
|
||||||
@ -161,9 +246,15 @@ export class DirectAuthService {
|
|||||||
|
|
||||||
// Reiniciar detección de inactividad
|
// Reiniciar detección de inactividad
|
||||||
this.resetInactivity();
|
this.resetInactivity();
|
||||||
|
|
||||||
|
// Reiniciar logs si están habilitados
|
||||||
|
if (this.ENABLE_ACTIVITY_LOGS && this.LOG_INTERVAL > 0) {
|
||||||
|
console.log('[Auth] Reiniciando logs después de renovación de token');
|
||||||
|
this.startActivityLogging();
|
||||||
|
}
|
||||||
}),
|
}),
|
||||||
catchError(error => {
|
catchError(error => {
|
||||||
console.error('Error al renovar token:', error);
|
// Error al renovar token
|
||||||
// Si falla la renovación, forzar cierre de sesión
|
// Si falla la renovación, forzar cierre de sesión
|
||||||
this.logout();
|
this.logout();
|
||||||
return throwError(() => new Error('Error al renovar la sesión'));
|
return throwError(() => new Error('Error al renovar la sesión'));
|
||||||
@ -204,7 +295,7 @@ export class DirectAuthService {
|
|||||||
|
|
||||||
return currentTime >= expirationTime;
|
return currentTime >= expirationTime;
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error al verificar expiración del token:', e);
|
// Error al verificar expiración del token
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -231,7 +322,7 @@ export class DirectAuthService {
|
|||||||
|
|
||||||
this.userInfo.set(user);
|
this.userInfo.set(user);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error al decodificar token:', e);
|
// Error al decodificar token
|
||||||
this.userInfo.set(null);
|
this.userInfo.set(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -256,11 +347,19 @@ export class DirectAuthService {
|
|||||||
const timeToExpiry = expirationTime - currentTime;
|
const timeToExpiry = expirationTime - currentTime;
|
||||||
const refreshTime = timeToExpiry * 0.7;
|
const refreshTime = timeToExpiry * 0.7;
|
||||||
|
|
||||||
|
// Calcular tiempos para la renovación del token
|
||||||
|
|
||||||
this.refreshTokenTimeout = setTimeout(() => {
|
this.refreshTokenTimeout = setTimeout(() => {
|
||||||
this.refreshToken().subscribe();
|
// Si STRICT_TOKEN_RENEWAL está activado, verificar la actividad del usuario
|
||||||
|
if (!this.STRICT_TOKEN_RENEWAL || this.isUserActive()) {
|
||||||
|
this.refreshToken().subscribe();
|
||||||
|
} else {
|
||||||
|
// En modo estricto, si el usuario está inactivo, cerrar sesión
|
||||||
|
this.logout();
|
||||||
|
}
|
||||||
}, refreshTime);
|
}, refreshTime);
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
console.error('Error al configurar renovación de token:', e);
|
// Error al configurar renovación de token
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -300,7 +399,7 @@ export class DirectAuthService {
|
|||||||
|
|
||||||
return Math.max(0, timeRemaining);
|
return Math.max(0, timeRemaining);
|
||||||
} catch (error) {
|
} catch (error) {
|
||||||
console.error('Error al obtener tiempo de expiración del token:', error);
|
// Error al obtener tiempo de expiración del token
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -333,8 +432,13 @@ export class DirectAuthService {
|
|||||||
|
|
||||||
// Reiniciar timer de inactividad basado en un porcentaje de la expiración del token
|
// Reiniciar timer de inactividad basado en un porcentaje de la expiración del token
|
||||||
private resetInactivity(): void {
|
private resetInactivity(): void {
|
||||||
|
// Registrar que el usuario está activo
|
||||||
|
this.lastActivityTime = Date.now();
|
||||||
|
|
||||||
|
// Marca el usuario como activo
|
||||||
this.userInactive.set(false);
|
this.userInactive.set(false);
|
||||||
|
|
||||||
|
// Limpiar cualquier temporizador existente
|
||||||
clearTimeout(this.userActivity);
|
clearTimeout(this.userActivity);
|
||||||
|
|
||||||
// Obtener el tiempo de expiración del token
|
// Obtener el tiempo de expiración del token
|
||||||
@ -345,9 +449,15 @@ export class DirectAuthService {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Calcular el tiempo de inactividad como el porcentaje configurado del tiempo de expiración
|
// Calcular el tiempo de inactividad como el porcentaje configurado del tiempo de expiración
|
||||||
const inactivityTime = tokenExpirationTime * this.INACTIVITY_PERCENTAGE;
|
const inactivityTime = Math.min(
|
||||||
|
tokenExpirationTime * this.INACTIVITY_PERCENTAGE,
|
||||||
|
this.MIN_INACTIVITY_TIME
|
||||||
|
);
|
||||||
|
|
||||||
|
// Configurar timer de inactividad
|
||||||
|
|
||||||
this.userActivity = setTimeout(() => {
|
this.userActivity = setTimeout(() => {
|
||||||
|
// Umbral de inactividad alcanzado
|
||||||
this.userInactive.set(true);
|
this.userInactive.set(true);
|
||||||
}, inactivityTime);
|
}, inactivityTime);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,5 +10,17 @@ export const environment = {
|
|||||||
},
|
},
|
||||||
api: {
|
api: {
|
||||||
baseUrl: '/api'
|
baseUrl: '/api'
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
// Tiempo mínimo de inactividad en milisegundos (5 minutos para producción)
|
||||||
|
minInactivityTime: 5 * 60 * 1000,
|
||||||
|
// Porcentaje del tiempo de vida del token para considerar inactividad (90%)
|
||||||
|
inactivityPercentage: 0.9,
|
||||||
|
// Estricto: true = no renovar si inactivo, false = renovar siempre
|
||||||
|
strictTokenRenewal: true,
|
||||||
|
// Intervalo de log en milisegundos (desactivado en producción)
|
||||||
|
logInterval: 0,
|
||||||
|
// Deshabilitar logs de inactividad en producción
|
||||||
|
enableActivityLogs: false
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -10,5 +10,17 @@ export const environment = {
|
|||||||
},
|
},
|
||||||
api: {
|
api: {
|
||||||
baseUrl: '/api'
|
baseUrl: '/api'
|
||||||
|
},
|
||||||
|
auth: {
|
||||||
|
// Tiempo mínimo de inactividad en milisegundos (30 segundos para desarrollo)
|
||||||
|
minInactivityTime: 30 * 1000,
|
||||||
|
// Porcentaje del tiempo de vida del token para considerar inactividad (90%)
|
||||||
|
inactivityPercentage: 0.9,
|
||||||
|
// Estricto: true = no renovar si inactivo, false = renovar siempre
|
||||||
|
strictTokenRenewal: true,
|
||||||
|
// Intervalo de log en milisegundos (1 segundo)
|
||||||
|
logInterval: 2000,
|
||||||
|
// Habilitar logs de inactividad
|
||||||
|
enableActivityLogs: true
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
@ -468,39 +468,60 @@ sudo useradd -r -s /sbin/nologin keycloak
|
|||||||
# Asignar permisos
|
# Asignar permisos
|
||||||
sudo chown -R keycloak:keycloak /opt/keycloak
|
sudo chown -R keycloak:keycloak /opt/keycloak
|
||||||
```
|
```
|
||||||
|
## Configurar usuario administrador inicial para Keycloak
|
||||||
### Configurar usuario administrador inicial para Keycloak
|
|
||||||
|
|
||||||
Crea un archivo de propiedades:
|
Crea un archivo de propiedades:
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
sudo nano /opt/keycloak/conf/keycloak.conf
|
sudo nano /opt/keycloak/conf/keycloak.conf
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Agrega estas líneas:
|
Agrega estas líneas:
|
||||||
|
|
||||||
|
> En mi caso esta máquina tiene la IP `192.168.1.27`, ajustar según sea necesario en una red real para la administración remota.
|
||||||
|
|
||||||
```
|
```
|
||||||
# Configuración básica
|
# Configuración básica
|
||||||
|
hostname=192.168.1.27
|
||||||
http-port=8080
|
http-port=8080
|
||||||
https-port=8443
|
http-enabled=true
|
||||||
hostname=localhost
|
|
||||||
|
|
||||||
# Configuración de administrador inicial
|
# Configuración de administrador inicial
|
||||||
http-enabled=true
|
http-enabled=true
|
||||||
```
|
|
||||||

|
|
||||||
|
|
||||||
### Iniciar Keycloak en modo desarrollo
|
```
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
## Crear un usuario administrador
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
cd /opt/keycloak
|
cd /opt/keycloak
|
||||||
export KEYCLOAK_ADMIN=admin
|
sudo -u keycloak /opt/keycloak/bin/kc.sh bootstrap-admin user --username admin
|
||||||
export KEYCLOAK_ADMIN_PASSWORD=admin
|
|
||||||
sudo -u keycloak bin/kc.sh start-dev
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
En este comando preguntará para crear la contraseña del usuario "admin" que usaremos como administrador.
|
||||||
|
|
||||||
### Configurar Keycloak como servicio (opcional)
|
## Iniciar Keycloak en modo desarrollo
|
||||||
|
|
||||||
|
Sin hacer cd, corremos el siguiente comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -u keycloak bin/kc.sh start-dev
|
||||||
|
|
||||||
|
```
|
||||||
|
|
||||||
|
## Iniciar Keycloak en modo productivo
|
||||||
|
|
||||||
|
Similar al anterior, sin embargo se le quita la flag `-dev` únicamente:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo -u keycloak bin/kc.sh start
|
||||||
|
```
|
||||||
|
|
||||||
|
## Configurar Keycloak como servicio productivo
|
||||||
|
|
||||||
En otra terminal, crea un archivo de servicio systemd:
|
En otra terminal, crea un archivo de servicio systemd:
|
||||||
|
|
||||||
@ -519,9 +540,7 @@ After=network.target
|
|||||||
Type=idle
|
Type=idle
|
||||||
User=keycloak
|
User=keycloak
|
||||||
Group=keycloak
|
Group=keycloak
|
||||||
Environment="KEYCLOAK_ADMIN=admin"
|
ExecStart=/opt/keycloak/bin/kc.sh start
|
||||||
Environment="KEYCLOAK_ADMIN_PASSWORD=admin"
|
|
||||||
ExecStart=/opt/keycloak/bin/kc.sh start-dev
|
|
||||||
TimeoutStartSec=600
|
TimeoutStartSec=600
|
||||||
TimeoutStopSec=600
|
TimeoutStopSec=600
|
||||||
|
|
||||||
@ -537,77 +556,96 @@ sudo systemctl enable keycloak
|
|||||||
sudo systemctl start keycloak
|
sudo systemctl start keycloak
|
||||||
```
|
```
|
||||||
|
|
||||||
## 5. Configuración de Keycloak en la interfaz web
|
Para ver el estado del servicio únicamente corre el comando:
|
||||||
|
|
||||||
|
```bash
|
||||||
|
sudo systemctl status keycloak
|
||||||
|
```
|
||||||
|
|
||||||
|
> **Nota**: si quieres ver el log del servicio usa el comando `sudo journalctl -u keycloak -f`
|
||||||
|
|
||||||
|
## 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:
|
Ahora puedes acceder a la consola de administración de Keycloak en http://localhost:8080/admin/ e iniciar sesión con:
|
||||||
|
|
||||||
- Usuario: `admin`
|
- Usuario: `admin`
|
||||||
- Contraseña: `admin`
|
- Contraseña: la que configuraste anteriormente
|
||||||
|
|
||||||
Esto iniciará Keycloak con un usuario administrador "admin" y contraseña "admin" tambien pedira cambiar la contraseña
|
> No necesariamente es ese login siempre, todo depende de qué usuario crearon anteriormente y su contraseña.
|
||||||

|
|
||||||
|
|
||||||
### Crear un nuevo Reino (Realm)
|

|
||||||
|
|
||||||
|
## Crear un nuevo Reino (Realm)
|
||||||
|
|
||||||
|
1. Haz clic en el menú desplegable superior izquierdo que dice "Manage realm"
|
||||||
|
2. Selecciona "Create Realm"
|
||||||
|
3. Ingresa el nombre: `angular-app`
|
||||||
|
4. Haz clic en "Create"
|
||||||
|
|
||||||
1. Haz clic en el menú desplegable superior izquierdo que dice "Manage realm"
|
|
||||||
2. Selecciona "Create Realm"
|
|
||||||
3. Ingresa el nombre: `angular-app`
|
|
||||||
4. Haz clic en "Create"
|
|
||||||

|

|
||||||
|
|
||||||
### Configurar la Federación de Usuarios LDAP
|
## Configurar la Federación de Usuarios LDAP
|
||||||
|
|
||||||
1. En el menú lateral izquierdo, selecciona "User Federation"
|
1. En el menú lateral izquierdo, selecciona "User Federation"
|
||||||
|
|
||||||
2. Haz clic en "Add Ldap provider"
|
2. Haz clic en "Add Ldap provider"
|
||||||

|
|
||||||
3. Completa los siguientes campos:
|

|
||||||
|
|
||||||
- Console Display Name: `LDAP`
|
3. Completa los siguientes campos:
|
||||||
- Vendor: `Other`
|
|
||||||
- Connection URL: `ldap://localhost:389`
|
- Console Display Name: `LDAP`
|
||||||
- Enable StartTLS: `OFF`
|
- Vendor: `Other`
|
||||||
- Bind Type: `simple`
|
- Connection URL: `ldap://localhost:389`
|
||||||
- Bind DN: `cn=admin,dc=correos,dc=com`
|
- Enable StartTLS: `OFF`
|
||||||
- Bind Credential: (la contraseña de admin LDAP)
|
- 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
|
||||||
|
|
||||||
|
|
||||||

|
> Más abajo hay más configuraciones pero se quedan con su valor por defecto. Después de guardar regresará a la pestaña anterior donde tendremos que presionar en la configuración recién creada.
|
||||||
- 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
|
|
||||||
|
|
||||||
> mas abajo hay mas configuraciones pero se quedan en su valor por defecto,
|
|
||||||
> despues de guardar regresara a la pestaña anterior donde tendremos que precionar en la configuracion recien creada
|
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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
|
6. En la pantalla del proveedor LDAP, ve a la pestaña "Synchronization"
|
||||||
|
|
||||||
1. En la pantalla del proveedor LDAP, ve a la pestaña "Mappers"
|
|
||||||

|
|
||||||
2. Haz clic en "Add mapper"
|
|
||||||

|
|
||||||
3.# Configuración Corregida del Mapeo de Grupos LDAP en Keycloak
|
|
||||||
|
|
||||||
3. En la pantalla del proveedor LDAP, ve a la pestaña "Mappers"
|
|
||||||
|

|
||||||
|
|
||||||
4. Haz clic en "Add mapper"
|
7. Haz clic en "Sync all users"
|
||||||
|
|
||||||
5. Completa los siguientes campos exactamente en este orden:
|
|
||||||
|
## Configurar el Mapeo de Grupos LDAP
|
||||||
|
|
||||||
|
1. En la pantalla del proveedor LDAP, ve a la pestaña "Mappers"
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
2. Haz clic en "Add mapper"
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
|
||||||
|
## Configuración del Mapeo de Grupos LDAP en Keycloak
|
||||||
|
|
||||||
|
3. Completa los siguientes campos exactamente en este orden:
|
||||||
|
|
||||||
- **Name**: `group-mapper`
|
- **Name**: `group-mapper`
|
||||||
- **Mapper type**: `group-ldap-mapper`
|
- **Mapper type**: `group-ldap-mapper`
|
||||||
@ -618,7 +656,7 @@ Esto iniciará Keycloak con un usuario administrador "admin" y contraseña "admi
|
|||||||
- **Preserve Group Inheritance**: `Off` _(¡IMPORTANTE! Debe estar desactivado para funcionar con Membership Attribute Type: UID)_
|
- **Preserve Group Inheritance**: `Off` _(¡IMPORTANTE! Debe estar desactivado para funcionar con Membership Attribute Type: UID)_
|
||||||
- **Ignore Missing Groups**: `Off`
|
- **Ignore Missing Groups**: `Off`
|
||||||
- **Membership LDAP Attribute**: `memberUid`
|
- **Membership LDAP Attribute**: `memberUid`
|
||||||
- **Membership Attribute Type**: `UID` _(¡IMPORTANTE! Debe ser UID, no DN )_
|
- **Membership Attribute Type**: `UID` _(¡IMPORTANTE! Debe ser UID, no DN)_
|
||||||
- **Membership User LDAP Attribute**: `uid`
|
- **Membership User LDAP Attribute**: `uid`
|
||||||
- **LDAP Filter**: (dejar en blanco)
|
- **LDAP Filter**: (dejar en blanco)
|
||||||
- **Mode**: `LDAP_ONLY`
|
- **Mode**: `LDAP_ONLY`
|
||||||
@ -627,46 +665,51 @@ Esto iniciará Keycloak con un usuario administrador "admin" y contraseña "admi
|
|||||||
- **Mapped Group Attributes**: (dejar en blanco)
|
- **Mapped Group Attributes**: (dejar en blanco)
|
||||||
- **Drop non-existing groups during sync**: `ON`
|
- **Drop non-existing groups during sync**: `ON`
|
||||||
- **Groups Path**: `/`
|
- **Groups Path**: `/`
|
||||||
6. Haz clic en "Save"
|
4. Haz clic en "Save"
|
||||||
|
|
||||||
|
|
||||||
|
> **Nota importante**: Esta configuración está optimizada para tu estructura LDAP donde se utiliza `posixGroup` con atributos `memberUid` simples (no DNs). El punto crítico para evitar errores es asegurarte de que **"Preserve Group Inheritance" esté desactivado (Off)** cuando usas el tipo de membresía UID.
|
||||||
|
|
||||||
> **Nota importante**: Esta configuración está optimizada para tu estructura LDAP donde se utiliza `posixGroup` con atributos `memberUid` simples (no DNs). El punto crítico para evitar el error es asegurarte de que **"Preserve Group Inheritance" esté desactivado (Off)** cuando usas el tipo de membresía UID.
|
|
||||||

|

|
||||||
|
|
||||||
|
|
||||||
> al igual que con el provedor ldap , al guardar se regresara a la pantalla anterior , donde tendremos que hacer click nuevamente en el mapper
|
|
||||||
|
|
||||||
7. En la pantalla del mapper, haz clic en "Sync LDAP Groups to Keycloak"
|
> Al igual que con el proveedor LDAP, al guardar se regresará a la pantalla anterior, donde tendremos que hacer clic nuevamente en el mapper.
|
||||||
|
|
||||||
|
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"
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
### Crear un Cliente para Angular
|
|
||||||
|
|
||||||
1. En el menú lateral izquierdo, selecciona "Clients"
|
> **Nota**: Para aplicaciones que no son SPA, puedes habilitar "Client authentication" y obtener un client secret que deberás usar en la configuración.
|
||||||
|
|
||||||
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
|
## 6. Integración con Angular: Enfoque básico
|
||||||
|
|
||||||
@ -1761,3 +1804,4 @@ Con la configuración y conocimientos adquiridos en este tutorial, estarás bien
|
|||||||
---
|
---
|
||||||
|
|
||||||
*Nota final: Recuerda que la seguridad es un proceso continuo, no un estado. Mantén todos los componentes actualizados y revisa regularmente las configuraciones y políticas de seguridad para adaptarte a nuevas amenazas y requisitos.*
|
*Nota final: Recuerda que la seguridad es un proceso continuo, no un estado. Mantén todos los componentes actualizados y revisa regularmente las configuraciones y políticas de seguridad para adaptarte a nuevas amenazas y requisitos.*
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user