Respostas:
Se você trabalha no .NET 3.5 ou mais recente, pode usar o System.DirectoryServices.AccountManagement
espaço para nome e verificar facilmente suas credenciais:
// 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");
}
É simples, é confiável, é um código gerenciado 100% C # do seu lado - o que mais você pode pedir? :-)
Leia tudo sobre isso aqui:
Atualizar:
Conforme descrito nesta outra pergunta do SO (e suas respostas) , há um problema com essa chamada possivelmente retornando True
senhas antigas de um usuário. Esteja ciente desse comportamento e não fique surpreso se isso acontecer :-) (obrigado a MikeGledhill por apontar isso!)
UserPrinciple.FindByIdentity
para verificar se o ID do usuário passado existe primeiro.
ContextOptions.Negotiate
.
Fazemos isso na nossa Intranet
Você precisa usar System.DirectoryServices;
Aqui estão as entranhas do 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();
}
}
}
strPassword
está armazenado no LDAP em texto sem formatação?
Close()
uma using
variável.
Várias soluções apresentadas aqui não têm a capacidade de diferenciar entre um usuário / senha incorretos e uma senha que precisa ser alterada. Isso pode ser feito da seguinte maneira:
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);
}
}
}
}
Se a senha do usuário estiver incorreta ou o usuário não existir, o erro conterá
"8009030C: LdapErr: DSID-0C0904DC, comentário: erro AcceptSecurityContext, dados 52e, v1db1",
se a senha do usuário precisar ser alterada, ela conterá
"8009030C: LdapErr: DSID-0C0904DC, comentário: erro AcceptSecurityContext, dados 773, v1db1"
O lexc.ServerErrorMessage
valor dos dados é uma representação hexadecimal do código de erro do Win32. Esses são os mesmos códigos de erro que seriam retornados invocando a chamada da API Win32 LogonUser. A lista abaixo resume um intervalo de valores comuns com valores hexadecimais e decimais:
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)
System.DirectoryServices
eSystem.DirectoryServices.Protocols
solução muito simples 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;
}
o acesso NativeObject é necessário para detectar um usuário / senha incorreto
PrincipleContext
- que só existe no .NET 3.5. Mas se você estiver usando o .NET 3.5 ou mais recente, você deve usarPrincipleContext
Infelizmente, não há uma maneira "simples" de verificar as credenciais de um usuário no AD.
Com todos os métodos apresentados até agora, você pode obter um falso negativo: os cleds de um usuário serão válidos, no entanto, o AD retornará false sob certas circunstâncias:
O ActiveDirectory não permitirá que você use LDAP para determinar se uma senha é inválida devido ao fato de um usuário precisar alterar a senha ou se a senha expirou.
Para determinar a alteração da senha ou a senha expirada, você pode chamar o Win32: LogonUser () e verificar o código de erro do Windows para as 2 seguintes constantes:
Provavelmente, a maneira mais fácil é PInvoke LogonUser Win32 API.eg
Referência do MSDN aqui ...
Definitivamente deseja usar o tipo de logon
LOGON32_LOGON_NETWORK (3)
Isso cria apenas um token leve - perfeito para verificações de AuthN. (outros tipos podem ser usados para criar sessões interativas etc.)
LogonUser
API exige que o usuário tenha o ato como parte da privelagem do sistema operacional ; o que não é algo que os usuários recebem - e não é algo que você deseja conceder a todos os usuários da organização. ( Msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx )
Uma solução .Net completa é usar as classes do espaço para nome System.DirectoryServices. Eles permitem consultar um servidor AD diretamente. Aqui está uma pequena amostra que faria isso:
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
Esse código se conecta diretamente ao servidor AD, usando as credenciais fornecidas. Se as credenciais forem inválidas, searcher.FindOne () lançará uma exceção. O ErrorCode é aquele que corresponde ao erro COM "nome de usuário / senha inválido".
Você não precisa executar o código como um usuário do AD. Na verdade, eu o utilizo com êxito para consultar informações em um servidor AD, de um cliente fora do domínio!
Ainda outra chamada .NET para autenticar rapidamente credenciais 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
}
}
Experimente este código (Observação: relatado para não funcionar no 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
exceto que você precisará criar sua própria exceção personalizada para "LogonException"
Se você está preso ao .NET 2.0 e ao código gerenciado, aqui está outra maneira de funcionar com contas locais e de domínio:
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;
}
A autenticação do Windows pode falhar por vários motivos: nome de usuário ou senha incorretos, conta bloqueada, senha expirada e muito mais. Para distinguir esses erros, chame a função API LogonUser via P / Invoke e verifique o código de erro se a função retornar 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 da amostra:
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: O LogonUser requer uma relação de confiança com o domínio contra o qual você está validando.
Minha função simples
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;
}
}
Aqui minha solução completa de autenticação para sua referência.
Primeiro, adicione as quatro referências a seguir
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 = "";
}
}