Bloquear cuenta

No siempre que quieres impedir el acceso de un usuario es necesario eliminar su cuenta. Hay situaciones en las que necesitas desactivar temporalmente una cuenta sin perder su configuración ni sus privilegios: un empleado sale de vacaciones prolongadas, se detecta actividad sospechosa, un contratista termina un proyecto pero podría volver en el futuro. Para estos casos MySQL ofrece el bloqueo de cuentas con ALTER USER ... ACCOUNT LOCK.

Una cuenta bloqueada conserva todos sus privilegios y su contraseña, pero cualquier intento de conexión será rechazado inmediatamente. Esto proporciona una capa de seguridad sin la irreversibilidad de DROP USER.

Sintaxis

La sintaxis para bloquear una cuenta es:

ALTER USER 'usuario'@'host' ACCOUNT LOCK;

También puedes crear un usuario que ya venga bloqueado desde el inicio:

CREATE USER 'usuario'@'host'
    IDENTIFIED BY 'contraseña'
    ACCOUNT LOCK;

Comportamiento básico

Cuando bloqueas una cuenta, MySQL establece el campo account_locked a Y en la tabla mysql.user. A partir de ese momento, cualquier intento de conexión con esa cuenta será rechazado con un mensaje de error específico.

ALTER USER 'contratista'@'%' ACCOUNT LOCK;

Si el usuario contratista intenta conectarse después del bloqueo:

ERROR 3118 (HY000): Access denied for user 'contratista'@'%'. Account is locked.

El mensaje de error indica claramente que la cuenta está bloqueada, lo que diferencia este rechazo de un error de contraseña incorrecta o de privilegios insuficientes.

Para verificar el estado de bloqueo de una cuenta:

SELECT user, host, account_locked
FROM mysql.user
WHERE user = 'contratista';
userhostaccount_locked
contratista%Y

Caso práctico: respuesta ante incidente de seguridad

Imagina que el equipo de seguridad detecta intentos de acceso sospechosos desde la cuenta de un empleado. Mientras se investiga el incidente, necesitas bloquear inmediatamente la cuenta sin destruir la evidencia que representan sus privilegios y configuración.

Primero, identifica y bloquea la cuenta comprometida:

ALTER USER 'sospechoso'@'192.168.1.%' ACCOUNT LOCK;

Luego, termina cualquier sesión activa que pueda existir:

SELECT id, user, host, db, time
FROM information_schema.processlist
WHERE user = 'sospechoso';
iduserhostdbtime
312sospechoso192.168.1.88:42100ventas45
315sospechoso192.168.1.88:42105clientes12
KILL 312;
KILL 315;

Ahora la cuenta está bloqueada y todas sus sesiones han sido terminadas. Los privilegios se conservan intactos para que el equipo de seguridad pueda auditarlos:

SHOW GRANTS FOR 'sospechoso'@'192.168.1.%';
Grants for sospechoso@192.168.1.%
GRANT USAGE ON . TO sospechoso@192.168.1.%
GRANT SELECT, INSERT, UPDATE, DELETE ON ventas.* TO sospechoso@192.168.1.%
GRANT SELECT ON clientes.* TO sospechoso@192.168.1.%

Si tras la investigación se determina que no hubo compromiso real, simplemente desbloqueas la cuenta. Si se confirma una brecha, puedes proceder a revocar privilegios y eliminar la cuenta con toda la información necesaria.

Caso práctico: gestión de personal temporal

En muchas empresas, los contratistas o empleados temporales trabajan por periodos definidos. En lugar de eliminar y recrear sus cuentas cada vez, puedes bloquearlas y desbloquearlas según sea necesario.

-- El contratista termina su periodo de trabajo
ALTER USER 'contratista_web'@'%' ACCOUNT LOCK;
 
-- Tres meses después, regresa para otro proyecto
ALTER USER 'contratista_web'@'%' ACCOUNT UNLOCK;

Para tener un registro claro de qué cuentas están bloqueadas actualmente:

SELECT user, host, account_locked, password_expired
FROM mysql.user
WHERE account_locked = 'Y'
  AND user NOT LIKE 'mysql.%'
ORDER BY user;
userhostaccount_lockedpassword_expired
contratista_web%YN
exempleado_analocalhostYN
test_temporal192.168.1.%YY

Esta consulta te da una vista rápida de todas las cuentas bloqueadas en el servidor, excluyendo las cuentas del sistema de MySQL.

Caso práctico: bloqueo automático por intentos fallidos

MySQL 8.0.19 introdujo la posibilidad de bloquear cuentas automáticamente después de un número determinado de intentos fallidos de inicio de sesión. Esto añade una capa de protección contra ataques de fuerza bruta.

-- Crear un usuario con bloqueo automático
CREATE USER 'usuario_seguro'@'%'
    IDENTIFIED BY 'Segur0#2024!'
    FAILED_LOGIN_ATTEMPTS 3
    PASSWORD_LOCK_TIME 1;

Esta configuración significa que después de tres intentos fallidos consecutivos de inicio de sesión, la cuenta se bloqueará automáticamente durante un día. Pasadas las veinticuatro horas, el bloqueo se levanta automáticamente.

Para aplicar esta política a un usuario existente:

ALTER USER 'empleado'@'localhost'
    FAILED_LOGIN_ATTEMPTS 5
    PASSWORD_LOCK_TIME 2;

Ahora empleado será bloqueado automáticamente después de cinco intentos fallidos, y el bloqueo durará dos días.

Si necesitas que el bloqueo sea permanente hasta que un administrador lo desbloquee manualmente:

ALTER USER 'cuenta_critica'@'localhost'
    FAILED_LOGIN_ATTEMPTS 3
    PASSWORD_LOCK_TIME UNBOUNDED;

Con PASSWORD_LOCK_TIME UNBOUNDED, la cuenta permanece bloqueada indefinidamente tras alcanzar el límite de intentos fallidos, requiriendo intervención manual de un administrador para restaurar el acceso.

Puedes verificar la configuración de bloqueo de cada usuario consultando la tabla information_schema.user_attributes:

SELECT user, host, attribute
FROM information_schema.user_attributes
WHERE user = 'usuario_seguro';

Advertencia: El bloqueo de cuenta no termina las sesiones ya establecidas. Si el usuario tenía una conexión activa antes del bloqueo, esa sesión seguirá funcionando. Siempre verifica y termina las sesiones activas después de bloquear una cuenta por razones de seguridad.

Consejo: Combinar el bloqueo de cuenta con la caducidad de contraseña proporciona una defensa en profundidad. Puedes bloquear la cuenta y además expirar su contraseña para que, cuando se desbloquee, el usuario deba establecer una nueva contraseña: ALTER USER 'usuario'@'host' ACCOUNT LOCK PASSWORD EXPIRE.

En el siguiente artículo aprenderás a desbloquear cuentas de usuario para restaurar el acceso.

Escrito por Eduardo Lázaro