¿Validar un nombre de usuario y contraseña en Active Directory?


527

¿Cómo puedo validar un nombre de usuario y contraseña en Active Directory? Simplemente quiero verificar si un nombre de usuario y contraseña son correctos.

Respuestas:


643

Si trabaja en .NET 3.5 o posterior, puede usar el System.DirectoryServices.AccountManagementespacio de nombres y verificar fácilmente sus credenciales:

// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "YOURDOMAIN"))
{
    // validate the credentials
    bool isValid = pc.ValidateCredentials("myuser", "mypassword");
}

Es simple, es confiable, es 100% C # código administrado en su extremo, ¿qué más puede pedir? :-)

Lea todo sobre esto aquí:

Actualizar:

Como se describe en esta otra pregunta SO (y sus respuestas) , existe un problema con esta llamada que posiblemente regrese Truepor las contraseñas antiguas de un usuario. Solo tenga en cuenta este comportamiento y no se sorprenda si esto sucede :-) (¡gracias a @MikeGledhill por señalar esto!)


36
En mi dominio, tuve que especificar pc.ValidateCredentials ("myuser", "mypassword", ContextOptions.Negotiate) o obtendría System.DirectoryServices.Protocols.DirectoryOperationException: el servidor no puede manejar las solicitudes de directorio.
Alex Peck

12
Si una contraseña caducó o las cuentas se deshabilitaron, ValidateCredentials devolverá false. Desafortunadamente, no le dice por qué se devuelve falso (lo cual es una pena, ya que significa que no puedo hacer algo sensato como redirigir al usuario para que cambie su contraseña).
Chris J

65
También tenga cuidado con la cuenta 'Invitado': si la cuenta Invitado de nivel de dominio está habilitada, ValidateCredentials devuelve verdadero si le da un usuario inexistente . Como resultado, es posible que desee llamar UserPrinciple.FindByIdentitypara ver si el ID de usuario pasado existe primero.
Chris J

77
@AlexPeck: la razón por la que tuvo que hacer esto (como yo) fue que .NET usa las siguientes tecnologías de forma predeterminada: LDAP + SSL, Kerberos, luego RPC. Sospecho que RPC está desactivado en su red (¡bien!) Y Kerberos en realidad no se usa por .NET a menos que explícitamente lo diga ContextOptions.Negotiate.
Brett Veenstra

55
Tenga en cuenta que si el usuario CAMBIA su contraseña de Active Directory, este código continuará autenticando felizmente al usuario con su contraseña AD anterior. Sí, de verdad. Lee aquí: stackoverflow.com/questions/8949501/…
Mike Gledhill

70

Hacemos esto en nuestra Intranet

Tienes que usar System.DirectoryServices;

Aquí están las entrañas del código

using (DirectoryEntry adsEntry = new DirectoryEntry(path, strAccountId, strPassword))
{
    using (DirectorySearcher adsSearcher = new DirectorySearcher(adsEntry))
    {
        //adsSearcher.Filter = "(&(objectClass=user)(objectCategory=person))";
        adsSearcher.Filter = "(sAMAccountName=" + strAccountId + ")";

        try
        {
            SearchResult adsSearchResult = adsSearcher.FindOne();
            bSucceeded = true;

            strAuthenticatedBy = "Active Directory";
            strError = "User has been authenticated by Active Directory.";
        }
        catch (Exception ex)
        {
            // Failed to authenticate. Most likely it is caused by unknown user
            // id or bad strPassword.
            strError = ex.Message;
        }
        finally
        {
            adsEntry.Close();
        }
    }
}

99
¿Qué pones en "camino"? El nombre del dominio? ¿El nombre del servidor? ¿La ruta LDAP al dominio? ¿La ruta LDAP al servidor?
Ian Boyd

3
Respuesta1: No, lo ejecutamos como un servicio web para que pueda llamarse desde múltiples ubicaciones en la aplicación web principal. Answer2: Path contiene información de LDAP ... LDAP: // DC = domainname1, DC = domainname2, DC = com
DiningPhilanderer

3
Parece que esto podría permitir la inyección de LDAP. Es posible que desee asegurarse de escapar o eliminar cualquier paréntesis en strAccountId
Brain2000

¿Esto significa que strPasswordse almacena en LDAP en texto sin formato?
Matt Kocaj

15
Nunca debería ser necesario invocar explícitamente Close()una usingvariable.
Nyerguds

62

Varias soluciones presentadas aquí carecen de la capacidad de diferenciar entre un usuario / contraseña incorrecto y una contraseña que debe cambiarse. Eso se puede hacer de la siguiente manera:

using System;
using System.DirectoryServices.Protocols;
using System.Net;

namespace ProtocolTest
{
    class Program
    {
        static void Main(string[] args)
        {
            try
            {
                LdapConnection connection = new LdapConnection("ldap.fabrikam.com");
                NetworkCredential credential = new NetworkCredential("user", "password");
                connection.Credential = credential;
                connection.Bind();
                Console.WriteLine("logged in");
            }
            catch (LdapException lexc)
            {
                String error = lexc.ServerErrorMessage;
                Console.WriteLine(lexc);
            }
            catch (Exception exc)
            {
                Console.WriteLine(exc);
            }
        }
    }
}

Si la contraseña del usuario es incorrecta o el usuario no existe, el error contendrá

"8009030C: LdapErr: DSID-0C0904DC, comentario: error AcceptSecurityContext, datos 52e, v1db1",

Si la contraseña del usuario necesita ser cambiada, contendrá

"8009030C: LdapErr: DSID-0C0904DC, comentario: error AcceptSecurityContext, datos 773, v1db1"

El lexc.ServerErrorMessagevalor de los datos es una representación hexadecimal del código de error Win32. Estos son los mismos códigos de error que se devolverían invocando la llamada a la API Win32 LogonUser. La siguiente lista resume un rango de valores comunes con valores hexadecimales y decimales:

525 user not found ​(1317)
52e invalid credentials ​(1326)
530 not permitted to logon at this time (1328)
531 not permitted to logon at this workstation (1329)
532 password expired ​(1330)
533 account disabled ​(1331) 
701 account expired ​(1793)
773 user must reset password (1907)
775 user account locked (1909)

2
Desafortunadamente, algunas instalaciones de AD no devuelven el código de sub-error LDAP, lo que significa que esta solución no funcionará.
Søren Mors

44
No olvide agregar algunas referencias al proyecto: System.DirectoryServicesySystem.DirectoryServices.Protocols
TomXP411

3
Sin embargo, mi pregunta es esta: ¿cómo se obtiene el nombre del servidor LDAP? Si está escribiendo una aplicación portátil, no puede esperar que el usuario sepa o necesite proporcionar los nombres de los servidores AD en cada red.
TomXP411

1
Tengo usuarios que están restringidos a iniciar sesión en estaciones de trabajo específicas; ¿Cómo especifico la estación de trabajo para la que estoy intentando iniciar sesión? (la estación de trabajo 1 fallaría con los datos 531, la estación de trabajo 2 funcionaría bien, por ejemplo)
akohlsmith

1
Me siento raro ya que no creo que estés recibiendo suficiente crédito. Este es un método totalmente administrado sin el problema de la llamada a la API Win32 para determinar si "el usuario debe restablecer la contraseña", lo que claramente ninguna de las otras respuestas logró. ¿Hay alguna laguna en este método que cause una tasa de apreciación baja? hmm ...
Lionet Chen

34

solución muy simple usando DirectoryServices:

using System.DirectoryServices;

//srvr = ldap server, e.g. LDAP://domain.com
//usr = user name
//pwd = user password
public bool IsAuthenticated(string srvr, string usr, string pwd)
{
    bool authenticated = false;

    try
    {
        DirectoryEntry entry = new DirectoryEntry(srvr, usr, pwd);
        object nativeObject = entry.NativeObject;
        authenticated = true;
    }
    catch (DirectoryServicesCOMException cex)
    {
        //not authenticated; reason why is in cex
    }
    catch (Exception ex)
    {
        //not authenticated due to some other exception [this is optional]
    }

    return authenticated;
}

Se requiere el acceso de NativeObject para detectar un usuario / contraseña incorrecto


44
Este código es malo porque también está haciendo una verificación de autorización (verifique si el usuario puede leer la información del directorio activo). El nombre de usuario y la contraseña pueden ser válidos, pero el usuario no puede leer información y obtener una excepción. En otras palabras, puede tener un nombre de usuario y contraseña válidos, pero aún así obtener una excepción.
Ian Boyd

2
De hecho, estoy en el proceso de pedir el equivalente nativo de PrincipleContext, que solo existe en .NET 3.5. Pero si está utilizando .NET 3.5 o más reciente, debería usarloPrincipleContext
Ian Boyd

28

Lamentablemente, no existe una forma "simple" de verificar las credenciales de los usuarios en AD.

Con cada método presentado hasta ahora, puede obtener un falso negativo: las credenciales de un usuario serán válidas, sin embargo, AD devolverá falso en ciertas circunstancias:

  • El usuario debe cambiar la contraseña en el siguiente inicio de sesión.
  • La contraseña del usuario ha expirado.

ActiveDirectory no le permitirá usar LDAP para determinar si una contraseña no es válida debido al hecho de que un usuario debe cambiar la contraseña o si su contraseña ha caducado.

Para determinar el cambio de contraseña o la contraseña caducada, puede llamar a Win32: LogonUser () y verificar el código de error de Windows para las siguientes 2 constantes:

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330

1
¿Puedo preguntar de dónde sacó los devinitions para expirado y MUST_CHANGE ... lo encontraron ninguna parte, pero aquí :)
mabstrei


Gracias. Intentaba descubrir cómo mi validación volvía falsa todo el tiempo. Fue porque el usuario necesita cambiar su contraseña.
Deise Vicentin el

22

Probablemente la forma más fácil es PInvocar LogonUser Win32 API.eg

http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html

Referencia de MSDN aquí ...

http://msdn.microsoft.com/en-us/library/aa378184.aspx

Definitivamente quiero usar el tipo de inicio de sesión

LOGON32_LOGON_NETWORK (3)

Esto crea solo un token liviano, perfecto para verificaciones AuthN. (se pueden usar otros tipos para crear sesiones interactivas, etc.)


Como señala @Alan, la API LogonUser tiene muchos rasgos útiles más allá de una llamada System.DirectoryServices.
stephbu

3
@cciotti: No, eso está mal. La MEJOR forma de autenticar correctamente a alguien es usar LogonUserAPI como @stephbu write. Todos los demás métodos descritos en esta publicación NO FUNCIONARÁN al 100%. Sin embargo, solo una nota, creo que debe estar unido al dominio para llamar a LogonUser.
Alan el

@Alan para generar credenciales, debe poder conectarse al dominio entregando una cuenta de dominio válida. Sin embargo, estoy bastante seguro de que su máquina no necesariamente necesita ser miembro del dominio.
stephbu

2
La LogonUserAPI requiere que el usuario tenga la Ley como parte del privilegio del sistema operativo ; que no es algo que obtienen los usuarios, y no es algo que desea otorgar a cada usuario de la organización. ( msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx )
Ian Boyd

1
LogonUser solo necesita Actuar como parte del sistema operativo para Windows 2000 y versiones posteriores de acuerdo con support.microsoft.com/kb/180548 ... Parece limpio para Server 2003 y superior.
Chris J

18

Una solución .Net completa es usar las clases del espacio de nombres System.DirectoryServices. Permiten consultar un servidor AD directamente. Aquí hay una pequeña muestra que haría esto:

using (DirectoryEntry entry = new DirectoryEntry())
{
    entry.Username = "here goes the username you want to validate";
    entry.Password = "here goes the password";

    DirectorySearcher searcher = new DirectorySearcher(entry);

    searcher.Filter = "(objectclass=user)";

    try
    {
        searcher.FindOne();
    }
    catch (COMException ex)
    {
        if (ex.ErrorCode == -2147023570)
        {
            // Login or password is incorrect
        }
    }
}

// FindOne() didn't throw, the credentials are correct

Este código se conecta directamente al servidor AD, utilizando las credenciales proporcionadas. Si las credenciales no son válidas, searcher.FindOne () arrojará una excepción. El Código de error es el que corresponde al error COM de "nombre de usuario / contraseña inválido".

No necesita ejecutar el código como usuario de AD. De hecho, ¡lo uso con éxito para consultar información en un servidor AD, desde un cliente fuera del dominio!


¿Qué hay de los tipos de autenticación? Creo que lo olvidó en su código anterior. :-) por defecto DirectoryEntry.AuthenticationType está configurado en Secured right? ese código no va a funcionar en LDAP que no están protegidos (Anónimo o Ninguno quizás). ¿Estoy en lo correcto con esto?
jerbersoft

El inconveniente de consultar un servidor AD es que tiene permiso para consultar el servidor AD. Su credencial puede ser válida, pero si no tiene permiso para consultar AD, obtendrá el error. Por eso se creó el llamado Fast Bind ; Usted valida las credenciales sin autorizar la capacidad del usuario para hacer algo.
Ian Boyd

2
¿Esto no permitiría que nadie pasara en caso de que se lanzara una COMException por alguna otra razón antes de que se verifiquen las credenciales?
Stefan Paul Noack

11

Otra llamada .NET para autenticar rápidamente las credenciales LDAP:

using System.DirectoryServices;

using(var DE = new DirectoryEntry(path, username, password)
{
    try
    {
        DE.RefreshCache(); // This will force credentials validation
    }
    catch (COMException ex)
    {
        // Validation failed - handle how you want
    }
}

Esta es la única solución que me ha funcionado, usar PrincipalContext no me ha funcionado.
Daniel

PrincipalContext no es válido para una conexión LDAP segura (también conocida como LDAPS, que utiliza el puerto 636
Kiquenet

10

Pruebe este código (NOTA: se informó que no funciona en Windows Server 2000)

#region NTLogonUser
#region Direct OS LogonUser Code
[DllImport( "advapi32.dll")]
private static extern bool LogonUser(String lpszUsername, 
    String lpszDomain, String lpszPassword, int dwLogonType, 
    int dwLogonProvider, out int phToken);

[DllImport("Kernel32.dll")]
private static extern int GetLastError();

public static bool LogOnXP(String sDomain, String sUser, String sPassword)
{
   int token1, ret;
   int attmpts = 0;

   bool LoggedOn = false;

   while (!LoggedOn && attmpts < 2)
   {
      LoggedOn= LogonUser(sUser, sDomain, sPassword, 3, 0, out token1);
      if (LoggedOn) return (true);
      else
      {
         switch (ret = GetLastError())
         {
            case (126): ; 
               if (attmpts++ > 2)
                  throw new LogonException(
                      "Specified module could not be found. error code: " + 
                      ret.ToString());
               break;

            case (1314): 
               throw new LogonException(
                  "Specified module could not be found. error code: " + 
                      ret.ToString());

            case (1326): 
               // edited out based on comment
               //  throw new LogonException(
               //   "Unknown user name or bad password.");
            return false;

            default: 
               throw new LogonException(
                  "Unexpected Logon Failure. Contact Administrator");
              }
          }
       }
   return(false);
}
#endregion Direct Logon Code
#endregion NTLogonUser

excepto que necesitará crear su propia excepción personalizada para "LogonException"


No utilice el manejo de excepciones para devolver información de un método. "Nombre de usuario desconocido o contraseña incorrecta" no es excepcional, es un comportamiento estándar para LogonUser. Solo devuelve falso.
Treb

sí ... este era un puerto de una vieja biblioteca VB6 ... escrito en 2003 más o menos ... (cuando salió .Net por primera vez)
Charles Bretana

Si se ejecuta en Windows 2000, este código no funcionará ( support.microsoft.com/kb/180548 )
Ian Boyd

1
Repensar esto. Iniciar sesión El comportamiento esperado del usuario, su propósito, es iniciar sesión en el usuario . Si no puede realizar esa tarea, ES una excepción. De hecho, el método debería devolver vacío, no un booleano. Además, si acaba de devolver un booleano, el consumidor del método no tiene forma de informar al usuario cuál fue el motivo del error.
Charles Bretana

5

Si está atascado con .NET 2.0 y el código administrado, esta es otra forma de trabajar con cuentas locales y de dominio:

using System;
using System.Collections.Generic;
using System.Text;
using System.Security;
using System.Diagnostics;

static public bool Validate(string domain, string username, string password)
{
    try
    {
        Process proc = new Process();
        proc.StartInfo = new ProcessStartInfo()
        {
            FileName = "no_matter.xyz",
            CreateNoWindow = true,
            WindowStyle = ProcessWindowStyle.Hidden,
            WorkingDirectory = Environment.GetFolderPath(Environment.SpecialFolder.CommonApplicationData),
            UseShellExecute = false,
            RedirectStandardError = true,
            RedirectStandardOutput = true,
            RedirectStandardInput = true,
            LoadUserProfile = true,
            Domain = String.IsNullOrEmpty(domain) ? "" : domain,
            UserName = username,
            Password = Credentials.ToSecureString(password)
        };
        proc.Start();
        proc.WaitForExit();
    }
    catch (System.ComponentModel.Win32Exception ex)
    {
        switch (ex.NativeErrorCode)
        {
            case 1326: return false;
            case 2: return true;
            default: throw ex;
        }
    }
    catch (Exception ex)
    {
        throw ex;
    }

    return false;
}   

Funciona bien con las cuentas locales de la máquina que lanzó el script
eka808

Por cierto, este método es necesario para que esto funcione público SecureString ToSecureString estático (string PwString) {char [] PasswordChars = PwString.ToCharArray (); SecureString Password = nueva SecureString (); foreach (char c en PasswordChars) Password.AppendChar (c); ProcessStartInfo foo = new ProcessStartInfo (); foo.Password = Contraseña; return foo.Password; }
eka808

Por el contrario, uno debería usar SecureString para contraseñas de todos modos. WPF PasswordBox lo admite.
Stephen Drew el

5

La autenticación de Windows puede fallar por varias razones: un nombre de usuario o contraseña incorrectos, una cuenta bloqueada, una contraseña caducada y más. Para distinguir entre estos errores, llame a la función API LogonUser a través de P / Invoke y verifique el código de error si la función devuelve false:

using System;
using System.ComponentModel;
using System.Runtime.InteropServices;

using Microsoft.Win32.SafeHandles;

public static class Win32Authentication
{
    private class SafeTokenHandle : SafeHandleZeroOrMinusOneIsInvalid
    {
        private SafeTokenHandle() // called by P/Invoke
            : base(true)
        {
        }

        protected override bool ReleaseHandle()
        {
            return CloseHandle(this.handle);
        }
    }

    private enum LogonType : uint
    {
        Network = 3, // LOGON32_LOGON_NETWORK
    }

    private enum LogonProvider : uint
    {
        WinNT50 = 3, // LOGON32_PROVIDER_WINNT50
    }

    [DllImport("kernel32.dll", SetLastError = true)]
    private static extern bool CloseHandle(IntPtr handle);

    [DllImport("advapi32.dll", SetLastError = true)]
    private static extern bool LogonUser(
        string userName, string domain, string password,
        LogonType logonType, LogonProvider logonProvider,
        out SafeTokenHandle token);

    public static void AuthenticateUser(string userName, string password)
    {
        string domain = null;
        string[] parts = userName.Split('\\');
        if (parts.Length == 2)
        {
            domain = parts[0];
            userName = parts[1];
        }

        SafeTokenHandle token;
        if (LogonUser(userName, domain, password, LogonType.Network, LogonProvider.WinNT50, out token))
            token.Dispose();
        else
            throw new Win32Exception(); // calls Marshal.GetLastWin32Error()
    }
}

Uso de la muestra:

try
{
    Win32Authentication.AuthenticateUser("EXAMPLE\\user", "P@ssw0rd");
    // Or: Win32Authentication.AuthenticateUser("user@example.com", "P@ssw0rd");
}
catch (Win32Exception ex)
{
    switch (ex.NativeErrorCode)
    {
        case 1326: // ERROR_LOGON_FAILURE (incorrect user name or password)
            // ...
        case 1327: // ERROR_ACCOUNT_RESTRICTION
            // ...
        case 1330: // ERROR_PASSWORD_EXPIRED
            // ...
        case 1331: // ERROR_ACCOUNT_DISABLED
            // ...
        case 1907: // ERROR_PASSWORD_MUST_CHANGE
            // ...
        case 1909: // ERROR_ACCOUNT_LOCKED_OUT
            // ...
        default: // Other
            break;
    }
}

Nota: LogonUser requiere una relación de confianza con el dominio contra el que se valida.


¿Puedes explicar por qué tu respuesta es mejor que la respuesta más votada?
Mohammad Ali

1
@MohammadAli: si necesita saber por qué falló la validación de credenciales (credenciales incorrectas, una cuenta bloqueada, una contraseña caducada, etc.), la función API LogonUser le informará. Por el contrario, el método PrincipalContext.ValidateCredentials (según los comentarios sobre la respuesta de marc_s) no lo hará; devuelve falso en todos estos casos. Por otro lado, LogonUser requiere una relación de confianza con el dominio, pero PrincipalContext.ValidateCredentials (creo) no.
Michael Liu

2

Mi simple función

 private bool IsValidActiveDirectoryUser(string activeDirectoryServerDomain, string username, string password)
    {
        try
        {
            DirectoryEntry de = new DirectoryEntry("LDAP://" + activeDirectoryServerDomain, username + "@" + activeDirectoryServerDomain, password, AuthenticationTypes.Secure);
            DirectorySearcher ds = new DirectorySearcher(de);
            ds.FindOne();
            return true;
        }
        catch //(Exception ex)
        {
            return false;
        }
    }

1

Aquí mi solución de autenticación completa para su referencia.

Primero, agregue las siguientes cuatro referencias

 using System.DirectoryServices;
 using System.DirectoryServices.Protocols;
 using System.DirectoryServices.AccountManagement;
 using System.Net; 

private void AuthUser() { 


      try{
            string Uid = "USER_NAME";
            string Pass = "PASSWORD";
            if (Uid == "")
            {
                MessageBox.Show("Username cannot be null");
            }
            else if (Pass == "")
            {
                MessageBox.Show("Password cannot be null");
            }
            else
            {
                LdapConnection connection = new LdapConnection("YOUR DOMAIN");
                NetworkCredential credential = new NetworkCredential(Uid, Pass);
                connection.Credential = credential;
                connection.Bind();

                // after authenticate Loading user details to data table
                PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
                UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Uid);
                DirectoryEntry up_User = (DirectoryEntry)user.GetUnderlyingObject();
                DirectorySearcher deSearch = new DirectorySearcher(up_User);
                SearchResultCollection results = deSearch.FindAll();
                ResultPropertyCollection rpc = results[0].Properties;
                DataTable dt = new DataTable();
                DataRow toInsert = dt.NewRow();
                dt.Rows.InsertAt(toInsert, 0);

                foreach (string rp in rpc.PropertyNames)
                {
                    if (rpc[rp][0].ToString() != "System.Byte[]")
                    {
                        dt.Columns.Add(rp.ToString(), typeof(System.String));

                        foreach (DataRow row in dt.Rows)
                        {
                            row[rp.ToString()] = rpc[rp][0].ToString();
                        }

                    }  
                }
             //You can load data to grid view and see for reference only
                 dataGridView1.DataSource = dt;


            }
        } //Error Handling part
        catch (LdapException lexc)
        {
            String error = lexc.ServerErrorMessage;
            string pp = error.Substring(76, 4);
            string ppp = pp.Trim();

            if ("52e" == ppp)
            {
                MessageBox.Show("Invalid Username or password, contact ADA Team");
            }
            if ("775​" == ppp)
            {
                MessageBox.Show("User account locked, contact ADA Team");
            }
            if ("525​" == ppp)
            {
                MessageBox.Show("User not found, contact ADA Team");
            }
            if ("530" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this time, contact ADA Team");
            }
            if ("531" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this workstation, contact ADA Team");
            }
            if ("532" == ppp)
            {
                MessageBox.Show("Password expired, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }



        } //common error handling
        catch (Exception exc)
        {
            MessageBox.Show("Invalid Username or password, contact ADA Team");

        }

        finally {
            tbUID.Text = "";
            tbPass.Text = "";

        }
    }
Al usar nuestro sitio, usted reconoce que ha leído y comprende nuestra Política de Cookies y Política de Privacidad.
Licensed under cc by-sa 3.0 with attribution required.