Quiero validar un conjunto de credenciales con el controlador de dominio. p.ej:
Username: STACKOVERFLOW\joel
Password: splotchy
Método 1. Consultar Active Directory con suplantación
Mucha gente sugiere consultar Active Directory por algo. Si se lanza una excepción, entonces sabrá que las credenciales no son válidas, como se sugiere en esta pregunta de stackoverflow .
Sin embargo, existen serios inconvenientes en este enfoque :
No solo está autenticando una cuenta de dominio, sino que también está realizando una verificación de autorización implícita. Es decir, está leyendo propiedades del AD utilizando un token de suplantación. ¿Qué sucede si la cuenta que de otro modo es válida no tiene derechos de lectura del AD? De forma predeterminada, todos los usuarios tienen acceso de lectura, pero las políticas de dominio se pueden configurar para deshabilitar los permisos de acceso para cuentas (o grupos) restringidos.
La vinculación contra AD tiene una sobrecarga importante, la caché del esquema de AD debe cargarse en el cliente (caché ADSI en el proveedor ADSI utilizado por DirectoryServices). Esto consume recursos tanto de la red como del servidor AD, y es demasiado caro para una operación simple como autenticar una cuenta de usuario.
Está confiando en una falla de excepción para un caso no excepcional, y asumiendo que eso significa un nombre de usuario y contraseña no válidos. Otros problemas (por ejemplo, falla de red, falla de conectividad de AD, error de asignación de memoria, etc.) se interpretan erróneamente como falla de autenticación.
Método 2. API LogonUser Win32
Otros han sugerido usar la LogonUser()
función API. Esto suena bien, pero desafortunadamente, el usuario que llama a veces necesita un permiso que generalmente solo se otorga al sistema operativo:
El proceso que llama a LogonUser requiere el privilegio SE_TCB_NAME. Si el proceso de llamada no tiene este privilegio, LogonUser falla y GetLastError devuelve ERROR_PRIVILEGE_NOT_HELD.
En algunos casos, el proceso que llama a LogonUser también debe tener habilitado el privilegio SE_CHANGE_NOTIFY_NAME; de lo contrario, LogonUser falla y GetLastError devuelve ERROR_ACCESS_DENIED. Este privilegio no es necesario para la cuenta del sistema local o las cuentas que son miembros del grupo de administradores. De forma predeterminada, SE_CHANGE_NOTIFY_NAME está habilitado para todos los usuarios, pero algunos administradores pueden deshabilitarlo para todos.
Distribuir el privilegio " Actuar como parte del sistema operativo " no es algo que quiera hacer, como lo señala Microsoft en un artículo de la base de conocimientos :
... el proceso que llama a LogonUser debe tener el privilegio SE_TCB_NAME (en el Administrador de usuarios, este es el derecho " Actuar como parte del sistema operativo "). El privilegio SE_TCB_NAME es muy poderoso y no debe otorgarse a ningún usuario arbitrario solo para que pueda ejecutar una aplicación que necesita validar las credenciales.
Además, una llamada a LogonUser()
fallará si se especifica una contraseña en blanco.
¿Cuál es la forma correcta de autenticar un conjunto de credenciales de dominio?
Resulta que estoy llamando desde un código administrado, pero esta es una pregunta general de Windows. Se puede suponer que los clientes tienen instalado .NET Framework 2.0.
fuente
Respuestas:
C # en .NET 3.5 usando System.DirectoryServices.AccountManagement .
bool valid = false; using (PrincipalContext context = new PrincipalContext(ContextType.Domain)) { valid = context.ValidateCredentials( username, password ); }
Esto validará contra el dominio actual. Consulte el constructor PrincipalContext parametrizado para ver otras opciones.
fuente
new PrincipalContext(ContextType.Machine)
lugar.using System; using System.Collections.Generic; using System.Linq; using System.Text; using System.Security; using System.DirectoryServices.AccountManagement; public struct Credentials { public string Username; public string Password; } public class Domain_Authentication { public Credentials Credentials; public string Domain; public Domain_Authentication(string Username, string Password, string SDomain) { Credentials.Username = Username; Credentials.Password = Password; Domain = SDomain; } public bool IsValid() { using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, Domain)) { // validate the credentials return pc.ValidateCredentials(Credentials.Username, Credentials.Password); } } }
fuente
Domain
parámetro al crear elPrincipalContext
, algo que estaba interesado en conocer y encontré en esta respuesta.Estoy usando el siguiente código para validar las credenciales. El método que se muestra a continuación confirmará si las credenciales son correctas y, en caso contrario, si la contraseña ha caducado o debe cambiarse.
He estado buscando algo como esto durante años ... ¡Así que espero que esto ayude a alguien!
using System; using System.DirectoryServices; using System.DirectoryServices.AccountManagement; using System.Runtime.InteropServices; namespace User { public static class UserValidation { [DllImport("advapi32.dll", SetLastError = true)] static extern bool LogonUser(string principal, string authority, string password, LogonTypes logonType, LogonProviders logonProvider, out IntPtr token); [DllImport("kernel32.dll", SetLastError = true)] static extern bool CloseHandle(IntPtr handle); enum LogonProviders : uint { Default = 0, // default for platform (use this!) WinNT35, // sends smoke signals to authority WinNT40, // uses NTLM WinNT50 // negotiates Kerb or NTLM } enum LogonTypes : uint { Interactive = 2, Network = 3, Batch = 4, Service = 5, Unlock = 7, NetworkCleartext = 8, NewCredentials = 9 } public const int ERROR_PASSWORD_MUST_CHANGE = 1907; public const int ERROR_LOGON_FAILURE = 1326; public const int ERROR_ACCOUNT_RESTRICTION = 1327; public const int ERROR_ACCOUNT_DISABLED = 1331; public const int ERROR_INVALID_LOGON_HOURS = 1328; public const int ERROR_NO_LOGON_SERVERS = 1311; public const int ERROR_INVALID_WORKSTATION = 1329; public const int ERROR_ACCOUNT_LOCKED_OUT = 1909; //It gives this error if the account is locked, REGARDLESS OF WHETHER VALID CREDENTIALS WERE PROVIDED!!! public const int ERROR_ACCOUNT_EXPIRED = 1793; public const int ERROR_PASSWORD_EXPIRED = 1330; public static int CheckUserLogon(string username, string password, string domain_fqdn) { int errorCode = 0; using (PrincipalContext pc = new PrincipalContext(ContextType.Domain, domain_fqdn, "ADMIN_USER", "PASSWORD")) { if (!pc.ValidateCredentials(username, password)) { IntPtr token = new IntPtr(); try { if (!LogonUser(username, domain_fqdn, password, LogonTypes.Network, LogonProviders.Default, out token)) { errorCode = Marshal.GetLastWin32Error(); } } catch (Exception) { throw; } finally { CloseHandle(token); } } } return errorCode; } }
fuente
A continuación, se explica cómo determinar un usuario local:
public bool IsLocalUser() { return windowsIdentity.AuthenticationType == "NTLM"; }
Editar por Ian Boyd
Ya no debería utilizar NTLM. Es tan antiguo y tan malo que el Verificador de aplicaciones de Microsoft (que se usa para detectar errores comunes de programación) lanzará una advertencia si detecta que usa NTLM.
Aquí hay un capítulo de la documentación del Verificador de aplicaciones sobre por qué tienen una prueba si alguien está usando NTLM por error:
fuente
using System; using System.Collections.Generic; using System.Text; using System.DirectoryServices.AccountManagement; class WindowsCred { private const string SPLIT_1 = "\\"; public static bool ValidateW(string UserName, string Password) { bool valid = false; string Domain = ""; if (UserName.IndexOf("\\") != -1) { string[] arrT = UserName.Split(SPLIT_1[0]); Domain = arrT[0]; UserName = arrT[1]; } if (Domain.Length == 0) { Domain = System.Environment.MachineName; } using (PrincipalContext context = new PrincipalContext(ContextType.Domain, Domain)) { valid = context.ValidateCredentials(UserName, Password); } return valid; } }
Kashif Mushtaq Ottawa, Canadá
fuente