Desarrollamos un formulario de inicio de sesión con Borland Delphi 6 (válido para otras versiones) y MySQL como motor de base de datos (válido para otros motores). Explicamos en este manual cómo hacer una ventana de validación de usuario usando algunos métodos de seguridad: guardando la contraseña del usuario en MD5 (hash), encriptando la contraseña de acceso a la base de datos, etc. Además, añadiremos la opción de obtener la IP del servidor de MySQL mediante AjpdSoft Aviso cambio IP pública. También mostramos cómo implementar la validación o inicio de sesión en un servidor LDAP (Microsoft Active Directory, dominio Windows u otros compatibles con LDAP).
- Diseño del formulario Delphi, componentes necesarios para validación de usuario.
- Tabla usuarios en la base de datos MySQL para validación de usuario en aplicación Delphi.
- Formulario de alta de usuario con cálculo de hash (md5) para guardar la contraseña.
- La seguridad para el inicio de sesión o validación de un usuario en nuestra aplicación con Delphi.
- Validar usuario en servidor LDAP con Delphi, implementar inicio de sesión login en LDAP con Delphi.
- Acceso a la tabla de parámetros mediante función Delphi y MySQL.
- Acceso nativo a MySQL Server con Delphi.
Diseño del formulario Delphi, componentes necesarios para validación de usuario
En primer lugar añadiremos un formulario a nuestro proyecto Delphi con los siguientes componentes:
- Puesto que nuestra aplicación permitirá validación de diferentes métodos, añadiremos varios TRadioButton para que el usuario pueda seleccionar el método de validación: aplicación, LDAP, Alfresco, etc. En este ejemplo implementaremos el método LDAP y el método aplicación.
- TEdit: para introducir el usuario y la contraseña.
- TButton: para botón «Iniciar», botón «Cancelar» y botón «Opciones».
- TabSheet: donde coloraremos los datos de acceso a la base de datos y los datos para obtener la dirección IP del servidor de base de datos.
Nota: la opción de «Dirección servidor BD IP» no es necesaria para un formulario de validación. Sirve para obtener la IP pública del servidor de bases de datos cuando ésta es dinámica. Si disponemos de IP estática o fija no será necesario. Esta opción enlaza con la base de datos de la aplicación AjpdSoft Aviso cambio IP pública que deberá estar iniciada en el servidor de MySQL para que obtenga la IP y la guarde en la base de datos.
En la siguiente descarga incluimos el código fuente completo de este formulario:
Tabla usuarios en la base de datos MySQL para validación de usuario en aplicación Delphi
Tabla de usuarios en MySQL para inicio de sesión, formulario de validación Delphi
Para el inicio de sesión en la aplicación desarrollada en Delphi usaremos una tabla donde guardaremos los datos de los usuarios. A continuación mostramos la consulta SQL de creación de esta tabla con todos los campos para MySQL:
CREATE TABLE usuario (
codigo int(10) unsigned NOT NULL AUTO_INCREMENT,
nick varchar(20) NOT NULL,
contrasena varchar(100),
codigocliente int(10) unsigned,
codigotecnico int(10) unsigned,
codusuarioa int(10) unsigned,
codusuariom int(10) unsigned,
fechaa datetime,
fecham datetime,
administrador char(1),
modificacion char(1),
nombre varchar(100),
fecha datetime,
accesoweb varchar(1),
codigodepartamento int(10) unsigned,
email varchar(200),
PRIMARY KEY (codigo),
UNIQUE KEY usuario_nick (nick)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
Por supuesto añadiremos todos los campos que necesitemos para los usuarios (DNI, Departamento, etc.), aunque los más importantes para la validación son nick (nombre de usuario) y contrasena (se guardará aquí el hash de la contraseña del usuario, NO la propia contraseña, por seguridad).
Para crear esta tabla en nuestro servidor de MySQL Server podremos usar cualquier aplicación que permita ejecutar sentencias SQL en MySQL Server, como por ejemplo la aplicación gratuita y con código fuente:
Por supuesto, también podremos usar MySQL Administrator o cualquier otro software para ejecutar consultas SQL en MySQL Server.
Tabla MySQL log inicio y cierre de sesión
En nuestro caso, aunque lógicamente no es necesario, usaremos también una tabla para guardar el inicio y cierre de sesión de cada usuario, para guardar un log de los accesos de los usuarios. Crearemos también la siguiente tabla:
CREATE TABLE sesion (
id int(11) NOT NULL AUTO_INCREMENT,
codigousuario int(11),
fecha date,
hora time,
estado varchar(20),
PRIMARY KEY (id)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
Como se puede observar, en la tabla «sesion» guardaremos el código del usuario que inicia la sesión, la fecha y la hora y el estado (si es inicio de sesión o es cierre de sesión). De esta forma quedará constancia de los accesos de cada usuario a la aplicación.
Tabla MySQL de parámetros de configuración de la aplicación
Crearemos una tercera tabla para guardar los parámetros de configuración de la aplicación:
CREATE TABLE parametro (
codigo int(10) unsigned NOT NULL AUTO_INCREMENT,
nombre varchar(50),
valor varchar(100),
PRIMARY KEY (codigo)
) ENGINE=InnoDB AUTO_INCREMENT=1 DEFAULT CHARSET=latin1
En dicha tabla guardaremos, por ejemplo, la versión de la aplicación. De forma que si desarrollamos una nueva versión de la aplicación, a los usuarios le aparezca un mensaje de aviso indicando que existe una nueva versión de la aplicación, incluso si es una versión «crítica» podemos obligar a los usuarios a que realicen la actualización. Aquí se puede ver un ejemplo de uso de esta tabla.
Formulario de alta de usuario con cálculo de hash (md5) para guardar la contraseña
Para el formulario de alta de nuevo usuario en nuestra aplicación Delphi, añadiremos los componentes necesarios para los campos de fecha de alta, nick, nombre completo, mail, contraseña, etc. A continuación mostramos un formulario de alta de usuario de ejemplo:
A continuación explicaremos algunas de las opciones más importantes del formulario de alta de usuario:
1. Cuando el usuario o el administrador de la aplicación introduzca la contraseña en el formulario, en la base de datos guardaremos el hash correspondiente a esta contraseña, nunca la contraseña directamente. De esta forma, si se produce un hackeo de nuestra base de datos, si un usuario malintencionado obtiene acceso a nuestra base de datos, al acceder a la tabla de usuarios no verá la contraseña del usuario. Para obtener el hash de la contraseña (que será el valor que guardemos en la base de datos) usaremos el componente gratuito para Delphi: TurboPower LockBox 2.07. Una vez instalado este componente, usaremos la siguiente función para obtener el Hash de la contraseña del usuario:
1 2 3 4 5 6 7 8 9 10 |
function obtenerHash (texto : string) : string; var MD5Digest : TMD5Digest; hashMD5 : TLbMD5; begin hashMD5 := TLbMD5.Create(nil); hashMD5.HashString(texto); hashMD5.GetDigest(MD5Digest); result := BufferToHex(MD5Digest, SizeOf(MD5Digest)); end; |
2. Al pulsar el botón «Aceptar», en el formulario de alta o modificación de usuario, comprobaremos si se ha cambiado la contraseña. Si el usuario ha cambiado la contraseña (una modificación) realizaremos la comprobación y guardaremos el hash en el componente enlazado con la base de datos, para guardarlo en la base de datos:
1 2 3 |
//si se ha cambiado la contraseña if contrasenaActual <> txtContrasena1.Text then txtContrasena1.Text := AnsiLowerCase(obtenerHash(txtContrasena1.Text)); |
La seguridad para el inicio de sesión o validación de un usuario en nuestra aplicación con Delphi
Para realizar un formulario seguro de inicio de sesión en nuestras aplicaciones con Delphi, usaremos los siguientes métodos de seguridad:
- En la tabla de usuario de MySQL no guardaremos directamente la contraseña, sino su hash (md5).
- Encriptar o cifrar la contraseña y usuario con los que accederemos a la base de datos MySQL, para ello usaremos el esquema de cifrado AES.
- Guardar log de inicio de sesión de cada usuario, para auditoría de accesos en caso necesario.
- Usar un usuario y contraseña de MySQL que sólo tenga permisos sobre las tablas de la aplicación, que no sea administrador de MySQL (no usar «root»).
La función para encriptar la contraseña y el nombre de usuario de acceso a la base de datos MySQL:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 |
//encriptar datos function encriptar (Str, Clave : String) : String; var Src: TStringStream; Dst: TMemoryStream; Size: Integer; Key: TAESKey; ExpandedKey: TAESExpandedKey; begin Result:= EmptyStr; Src:= TStringStream.Create(Str); try Dst:= TMemoryStream.Create; try // Preparamos la clave, lo ideal es que tenga 32 caracteres FillChar(Key,Sizeof(Key),#0); if Length(Clave) > Sizeof(Key) then move(PChar(Clave)^,Key,Sizeof(key)) else move(PChar(Clave)^,Key,Length(Clave)); AEsExpandKey(ExpandedKey,Key); // Guardamos el tamaño del texto original Size:= Src.Size; Dst.WriteBuffer(Size,Sizeof(Size)); // Ciframos el texto AESEncryptStreamECB(Src,Dst,ExpandedKey); // Lo codificamos a base64 Result:= BinToStr(Dst.Memory,Dst.Size); finally Dst.Free; end; finally Src.Free; end; end; |
La función para desencriptar la contraseña y usuario:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 |
//desencriptar datos function desencriptar (Str, Clave : String): String; var Src: TMemoryStream; Dst: TStringStream; Size: Integer; Key: TAESKey; ExpandedKey: TAESExpandedKey; begin Result:= EmptyStr; Src:= TMemoryStream.Create; try Dst:= TStringStream.Create(Str); try StrToStream(Str, Src); Src.Position:= 0; FillChar(Key,Sizeof(Key),#0); if Length(Clave) > Sizeof(Key) then move(PChar(Clave)^,Key,Sizeof(key)) else move(PChar(Clave)^,Key,Length(Clave)); AESExpandKey(ExpandedKey,Key); // Leemos el tamaño del texto try Src.ReadBuffer(Size,Sizeof(Size)); AESDecryptStreamECB(Src,Dst,ExpandedKey); Dst.Size:= Size; Result:= Dst.DataString; except Result := ''; end; finally Dst.Free; end; finally Src.Free; end; end; |
En la cláusula «Uses» de la unidad donde coloquemos los procedimientos anteriores necesitaremos añadir:
1 2 3 4 5 6 7 |
unit UnidadProcedimientos; interface uses inifiles, sysutils, ShlObj, ActiveX, Windows, ShellCtrls, controls, classes, registry, db, dialogs, forms, variants, IdSMTP, IdMessage, dateutils, shellapi, mapi, LbClass, LbCipher, LbUtils, aes, base64, StrUtils; |
Para guardar la contraseña en el fichero INI de configuración de la aplicación usaremos:
1 2 3 4 5 |
esCadINI('Base de datos', 'Tipo BD MySQL - Usuario', trim(encriptar (txtBDUsuario.Text, claveEncriptacionTexto))); esCadINI('Base de datos', 'Tipo BD MySQL - Contraseña', trim(encriptar (txtBDContrasena.Text, claveEncriptacionTexto))); |
El procedimiento «esCadINI»:
1 2 3 4 5 6 7 8 9 10 |
//escribe un valor string en un fichero INI procedure esCadINI (clave, cadena : string; valor : String); begin with tinifile.create (changefileext(paramstr(0),'.INI')) do try WriteString (clave, cadena, valor); finally free; end; end; |
El procedimiento «esCadINI» requerirá añadir en el Uses de la unidad inifiles:
1 2 3 4 5 6 7 |
unit UnidadProcedimientos; interface uses inifiles, sysutils, ShlObj, ActiveX, Windows, ShellCtrls, controls, classes, registry, db, dialogs, forms, variants, IdSMTP, IdMessage, dateutils, shellapi, mapi, LbClass, LbCipher, LbUtils, aes, base64, StrUtils; |
También necesitaremos declarar la constante claveEncriptacionTexto con el valor de la clave de encriptación, necesaria para encriptar y desencriptar, es recomendable que sea una clave con números, letras, mayúsculas, minúsculas y algún carácter especial:
1 2 3 4 |
//para encriptación de contraseñas en AES claveEncriptacionTexto = 'AnJtP1DdSñO2FwT8'; implementation |
Y, lógicamente, necesitaremos los ficheros AES.pas y base64.pas que se incluyen en la descarga:
Validar usuario en servidor LDAP con Delphi, implementar inicio de sesión login en LDAP con Delphi
Para permitir la validación en un servidor con el protocolo LDAP (dominio de Microsoft Windows con Active Directory, OpenLDAP, etc.) añadiremos los siguientes ficheros a nuestro proyecto Delphi: Cert.pas, Constant.pas, Events.pas, Gss.pas, LDAPClasses.pas, Misc.pas. Todos estos ficheros se incluyen en la descarga:
Por supuesto, en la descarga anterior también incluimos el formulario de inicio de sesión con el código fuente necesario para validar o autenticar un usuario en un servidor LDAP.
El código que usamos en el formulario de inicio de sesión para la autenticación, validación o login de un usuario en LDAP con Delphi:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 |
if opValLDAP.Checked then begin if ((txtValLDAPServidor.Text = '') or (txtValLDAPDominio.Text = '')) then begin continuar := false; MessageDlg('Para la validación con LDAP debe indicar el servidor y el dominio.', mtWarning, [mbok], 0); tabLDAP.Show; end else begin if autenticarLDAP (txtUsuario.Text, txtContrasena.Text, txtValLDAPServidor.Text, txtValLDAPDominio.Text) then begin continuar := True; end else begin continuar := false; MessageDlg('Error de validación LDAP, compruebe que existe el usuario [' + txtUsuario.Text + '] en el servidor LDAP: [' + txtValLDAPServidor.Text + '] del dominio [' + txtValLDAPDominio.Text + '] o que la contraseña es correcta.', mtWarning, [mbok], 0); end; end; end; |
La función «autenticarLDAP» la podremos encontrar en el fichero «UnidadProcedimientos.pas», es la siguiente:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 |
function autenticarLDAP (usuario : string; contrasena : string; servidor : string; dominio : string) : boolean; var e : array[0..0] of string; //i : integer; begin LDAPSession := TLDAPSession.Create; LDAPEntryList := TLDAPEntryList.create; LDAPSession.Server := servidor; LDAPSession.Base := 'dc=' + servidor + ',dc=' + dominio + ',dc=com'; LDAPSESSION.PagedSearch := TRUE; LDAPSEssion.PageSize := 100; LDAPSession.SSL := false; LDAPSession.DereferenceAliases := 0; LDAPSession.Version := 3; LDAPSEssion.AuthMethod := 0; LDAPSession.User := usuario; LDAPSession.Password := contrasena; LdapSession.ChaseReferrals := true; ldapsession.ReferralHops := 32; LDAPSession.Connect; e[0] := 'cn'; if ldapsession.Connected then Result := true else Result := false; end; |
Para validar o autenticar un usuario en un servidor con el protocolo LDAP hemos usado la siguiente aplicación de ejemplo del AjpdSoft Validación LDAP y el siguiente paquete de unidades para acceso a LDAP: ADSI (Active Directory Service Interfaces).
Acceso a la tabla de parámetros mediante función Delphi y MySQL
El acceso a la tabla de parámetros de la aplicación, donde guardaremos los valores de configuración, se realizará con una función como la siguiente, donde leeremos el valor del parámetro que necesitemos en cada momento:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
procedure obtenerValorParametro (nombre : string; var valor1 : string; var valor2 : string); begin md.tc.Close; md.tc.SQL.Clear; md.tc.SQL.Add('SELECT valor, valor2'); md.tc.SQL.Add('FROM ' + vtTablaParametro); md.tc.SQL.Add('WHERE nombre = :pNombre'); md.tc.ParamByName('pNombre').DataType := ftinteger; md.tc.ParamByName('pNombre').AsString := nombre; md.tc.Open; valor1 := md.tc.FieldByName('valor').AsString; valor2 := md.tc.FieldByName('valor2').AsString; md.tc.Close; end; |
Por ejemplo, para obtener la versión de la aplicación y si es distinta no iniciarla, utilizaremos el siguiente código:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 |
//comprobamos la versión de la aplicación obtenerValorParametro('version', valorParametro1, valorParametro2); versionBD := valorParametro1; accesoSiDistinta := valorParametro2; if (versionBD <> vtVersionAplicacion) then begin MessageDlg('La versión actual de su aplicación [' + vtVersionAplicacion + '] es diferente a la de la BD [' + versionBD + ']. ' + chr(13) + chr(13) + 'Le recomendamos que actualice ' + 'su versión de software para evitar incongruencias ' + 'en los datos.', mtInformation, [mbok], 0); if accesoSiDistinta = 'N' then Application.Terminate; end; |
De esta forma podremos acceder a cualquier parámetro de la aplicación y éste quedará guardado en la base de datos, por lo que serán parámetros compartidos para todos los usuarios de la aplicación.
Acceso nativo a MySQL Server con Delphi
Para el acceso al servidor de bases de datos MySQL Server con el lenguaje de programación Borland Delphi 6 hemos usado el componente gratuito Zeosdbo 6.5.1. En el siguiente enlace explicamos cómo instalarlo en Delphi 6:
Este componente tiene la ventaja de que accede de forma nativa (sin usar intermediarios como ODBC) al servidor de MySQL Server indicado. Es bastante sencillo de programar, es suficiente con añadir un componente de tipo ZConnection:
Estableceremos las propiedades del componente ZConnection por código, obteniendo los datos del servidor de MySQL Server al que nos conectaremos desde el formulario de login:
1 2 3 4 5 6 7 8 9 10 11 12 |
if continuar then begin md.bd.Disconnect; md.bd.Database := txtBDBD.Text; md.bd.User := txtBDUsuario.Text; md.bd.Password := txtBDContrasena.Text; md.bd.HostName := txtBDServidor.Text; md.bd.Port := strtoint(txtBDPuerto.Text); md.bd.Protocol := txtBDProtocolo.Text; try md.bd.Connect; ...... |
Añadiremos el resto de componentes Zeosdbo, todos ellos enlazados con el ZConnection anterior mediante la propiedad Connection. Por ejemplo, para ejecutar consultas SQL en nuestro MySQL Server y obtener el resultado usaremos un ZQuery:
Un ejemplo de uso del ZQuery:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
vtUsuario := txtBDUsuario.Text; md.tc.Close; md.tc.SQL.Clear; md.tc.SQL.Add('SELECT codigo, contrasena, nombre, modificacion, '); md.tc.SQL.Add(' administrador, tipo'); md.tc.SQL.Add('FROM usuario'); md.tc.SQL.Add('WHERE upper(nick) = :pNick and activo = "S"'); md.tc.ParamByName('pNick').DataType := ftString; md.tc.ParamByName('pNick').Value := ansiuppercase(txtUsuario.Text); md.tc.Open; if md.tc.RecordCount > 0 then begin vtcontrasenaUsuario := md.tc.FieldByName('contrasena').AsString; if opValGISAM.Checked then |
Podremos consultar y usar el código fuente completo del formulario de inicio de sesión realizando la descarga: