Quero validar um conjunto de credenciais em relação ao controlador de domínio. por exemplo:
Username: STACKOVERFLOW\joel
Password: splotchy
Método 1. Consultar o Active Directory com representação
Muitas pessoas sugerem consultar o Active Directory para algo. Se uma exceção for lançada, você saberá que as credenciais não são válidas - como é sugerido nesta questão stackoverflow .
No entanto, existem algumas desvantagens sérias nessa abordagem :
Você não está apenas autenticando uma conta de domínio, mas também fazendo uma verificação de autorização implícita. Ou seja, você está lendo propriedades do AD usando um token de representação. E se a conta válida de outra forma não tiver direitos de leitura do AD? Por padrão, todos os usuários têm acesso de leitura, mas as políticas de domínio podem ser definidas para desabilitar as permissões de acesso para contas restritas (e / ou grupos).
A vinculação ao AD tem uma sobrecarga séria, o cache do esquema do AD deve ser carregado no cliente (cache ADSI no provedor ADSI usado por DirectoryServices). Isso consome recursos da rede e do servidor AD - e é muito caro para uma operação simples como a autenticação de uma conta de usuário.
Você está contando com uma falha de exceção para um caso não excepcional e presumindo que isso significa nome de usuário e senha inválidos. Outros problemas (por exemplo, falha de rede, falha de conectividade AD, erro de alocação de memória, etc.) são então interpretados incorretamente como falha de autenticação.
Método 2. API LogonUser Win32
Outros sugeriram usar a LogonUser()
função API. Isso parece bom, mas infelizmente o usuário que faz a chamada às vezes precisa de uma permissão geralmente concedida apenas ao próprio sistema operacional:
O processo de chamada de LogonUser requer o privilégio SE_TCB_NAME. Se o processo de chamada não tiver esse privilégio, LogonUser falha e GetLastError retorna ERROR_PRIVILEGE_NOT_HELD.
Em alguns casos, o processo que chama LogonUser também deve ter o privilégio SE_CHANGE_NOTIFY_NAME habilitado; caso contrário, LogonUser falha e GetLastError retorna ERROR_ACCESS_DENIED. Este privilégio não é necessário para a conta do sistema local ou contas que são membros do grupo de administradores. Por padrão, SE_CHANGE_NOTIFY_NAME está habilitado para todos os usuários, mas alguns administradores podem desabilitá-lo para todos.
Distribuir o privilégio " Agir como parte do sistema operacional " não é algo que você queira fazer quer queira quer não - como a Microsoft aponta em um artigo da base de conhecimento :
... o processo que está chamando LogonUser deve ter o privilégio SE_TCB_NAME (no Gerenciador de usuários, esse é o direito " Agir como parte do sistema operacional "). O privilégio SE_TCB_NAME é muito poderoso e não deve ser concedido a nenhum usuário arbitrário apenas para que ele possa executar um aplicativo que precise validar credenciais.
Além disso, uma chamada para LogonUser()
falhará se uma senha em branco for especificada.
Qual é a maneira correta de autenticar um conjunto de credenciais de domínio?
Acontece que estou ligando de código gerenciado, mas esta é uma questão geral do Windows. Pode-se presumir que os clientes tenham o .NET Framework 2.0 instalado.
fonte
Respostas:
C # no .NET 3.5 usando System.DirectoryServices.AccountManagement .
bool valid = false; using (PrincipalContext context = new PrincipalContext(ContextType.Domain)) { valid = context.ValidateCredentials( username, password ); }
Isso irá validar contra o domínio atual. Confira o construtor PrincipalContext parametrizado para outras opções.
fonte
new PrincipalContext(ContextType.Machine)
.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); } } }
fonte
Domain
parâmetro ao criar oPrincipalContext
, algo que eu estava interessado em saber e encontrei nesta resposta.Estou usando o seguinte código para validar as credenciais. O método mostrado abaixo irá confirmar se as credenciais estão corretas e, caso contrário, se a senha expirou ou precisa ser alterada.
Há anos que procuro algo assim ... Espero que ajude alguém!
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; } }
fonte
Veja como determinar um usuário local:
public bool IsLocalUser() { return windowsIdentity.AuthenticationType == "NTLM"; }
Editado por Ian Boyd
Você não deve mais usar NTLM. É tão antigo e tão ruim que o Application Verifier da Microsoft (usado para detectar erros de programação comuns) emitirá um aviso se detectar o uso de NTLM.
Aqui está um capítulo da documentação do Application Verifier sobre por que eles têm um teste se alguém está usando NTLM por engano:
fonte
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á
fonte