Control de acceso

MySQL usa un sistema de dos niveles para controlar el acceso a los procedimientos almacenados: quién puede ejecutarlos y con qué permisos se ejecutan. Esto permite crear una capa de seguridad donde los usuarios acceden a datos a través de procedimientos sin tener acceso directo a las tablas.

SQL SECURITY: DEFINER vs INVOKER

ModoEjecuta con permisos deUso típico
DEFINEREl usuario que creó el procedimientoDar acceso controlado a datos
INVOKEREl usuario que llama al procedimientoRespetar permisos del usuario

DEFINER: permisos del creador

DELIMITER //
 
CREATE PROCEDURE sp_datos_sensibles()
SQL SECURITY DEFINER
BEGIN
    SELECT id, nombre, email, telefono
    FROM clientes
    ORDER BY nombre;
END //
 
DELIMITER ;

Con SQL SECURITY DEFINER, un usuario sin permiso SELECT en la tabla clientes puede ejecutar este procedimiento si tiene permiso EXECUTE, porque se ejecuta con los permisos del creador.

INVOKER: permisos del que llama

DELIMITER //
 
CREATE PROCEDURE sp_mis_datos()
SQL SECURITY INVOKER
BEGIN
    SELECT id, nombre, email
    FROM clientes;
END //
 
DELIMITER ;

Con SQL SECURITY INVOKER, el procedimiento falla si el usuario que lo llama no tiene permiso SELECT en la tabla clientes.

GRANT EXECUTE

-- Dar permiso para ejecutar un procedimiento específico
GRANT EXECUTE ON PROCEDURE tienda_mysql.sp_datos_sensibles TO 'usuario_app'@'localhost';
 
-- Dar permiso para ejecutar todos los procedimientos de la base de datos
GRANT EXECUTE ON tienda_mysql.* TO 'usuario_app'@'localhost';
 
-- Revocar permiso
REVOKE EXECUTE ON PROCEDURE tienda_mysql.sp_datos_sensibles FROM 'usuario_app'@'localhost';

Ejemplo práctico: acceso controlado

Supongamos que queremos que un usuario de la aplicación pueda consultar y modificar pedidos sin acceso directo a las tablas:

-- Procedimiento para crear pedido (DEFINER)
DELIMITER //
 
CREATE PROCEDURE sp_crear_pedido(
    IN p_cliente_id INT,
    IN p_producto_id INT,
    IN p_cantidad INT,
    OUT p_pedido_id INT
)
SQL SECURITY DEFINER
MODIFIES SQL DATA
COMMENT 'Crea un nuevo pedido con validación'
BEGIN
    DECLARE v_precio DECIMAL(10,2);
    DECLARE v_stock INT;
 
    -- Validar stock
    SELECT precio, stock INTO v_precio, v_stock
    FROM productos WHERE id = p_producto_id AND activo = TRUE;
 
    IF v_stock IS NULL THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Producto no encontrado o inactivo';
    END IF;
 
    IF v_stock < p_cantidad THEN
        SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Stock insuficiente';
    END IF;
 
    -- Crear pedido
    INSERT INTO pedidos (cliente_id, fecha, total, estado)
    VALUES (p_cliente_id, CURDATE(), v_precio * p_cantidad, 'pendiente');
 
    SET p_pedido_id = LAST_INSERT_ID();
 
    INSERT INTO detalle_pedidos (pedido_id, producto_id, cantidad, precio_unitario)
    VALUES (p_pedido_id, p_producto_id, p_cantidad, v_precio);
 
    UPDATE productos SET stock = stock - p_cantidad WHERE id = p_producto_id;
END //
 
DELIMITER ;
-- Procedimiento para consultar pedidos del cliente
DELIMITER //
 
CREATE PROCEDURE sp_mis_pedidos(IN p_cliente_id INT)
SQL SECURITY DEFINER
READS SQL DATA
BEGIN
    SELECT p.id, p.fecha, p.total, p.estado,
           GROUP_CONCAT(pr.nombre SEPARATOR ', ') AS productos
    FROM pedidos p
    JOIN detalle_pedidos dp ON p.id = dp.pedido_id
    JOIN productos pr ON dp.producto_id = pr.id
    WHERE p.cliente_id = p_cliente_id
    GROUP BY p.id, p.fecha, p.total, p.estado
    ORDER BY p.fecha DESC;
END //
 
DELIMITER ;
-- Dar permisos al usuario de la aplicación
-- GRANT EXECUTE ON PROCEDURE tienda_mysql.sp_crear_pedido TO 'app_user'@'localhost';
-- GRANT EXECUTE ON PROCEDURE tienda_mysql.sp_mis_pedidos TO 'app_user'@'localhost';

El usuario app_user puede crear y consultar pedidos sin tener SELECT, INSERT o UPDATE en las tablas directamente.

Ver el DEFINER

SELECT ROUTINE_NAME, DEFINER, SECURITY_TYPE
FROM information_schema.ROUTINES
WHERE ROUTINE_SCHEMA = 'tienda_mysql'
AND ROUTINE_TYPE = 'PROCEDURE';
ROUTINE_NAMEDEFINERSECURITY_TYPE
sp_datos_sensiblesroot@localhostDEFINER
sp_mis_datosroot@localhostINVOKER
sp_crear_pedidoroot@localhostDEFINER
sp_mis_pedidosroot@localhostDEFINER

Cambiar el modo de seguridad

ALTER PROCEDURE sp_mis_datos
SQL SECURITY DEFINER;

Ver permisos de un usuario

SHOW GRANTS FOR 'usuario_app'@'localhost';

Recomendaciones

EscenarioRecomendación
API de datos para aplicacionesDEFINER con GRANT EXECUTE
Procedimientos administrativosINVOKER para respetar permisos
Reportes para usuarios limitadosDEFINER con acceso controlado
Procedimientos internosDEFINER es suficiente

Limpieza

DROP PROCEDURE IF EXISTS sp_datos_sensibles;
DROP PROCEDURE IF EXISTS sp_mis_datos;
DROP PROCEDURE IF EXISTS sp_crear_pedido;
DROP PROCEDURE IF EXISTS sp_mis_pedidos;

Con esto completamos la sección de procedimientos almacenados. Hemos cubierto desde la creación básica hasta el control de flujo, cursores y seguridad. En la siguiente sección exploraremos el manejo de errores dentro de procedimientos.

Escrito por Eduardo Lázaro