Risposte:
Se lavori su .NET 3.5 o versioni successive, puoi utilizzare lo System.DirectoryServices.AccountManagement
spazio dei nomi e verificare facilmente le tue credenziali:
// 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");
}
È semplice, è affidabile, è un codice gestito al 100% C # da parte tua: cosa puoi chiedere di più? :-)
Leggi tutto qui:
Aggiornare:
Come indicato in questa altra domanda SO (e nelle sue risposte) , c'è un problema con questa chiamata che potrebbe tornare True
per vecchie password di un utente. Basta essere consapevoli di questo comportamento e non essere troppo sorpreso se ciò accade :-) (grazie a @MikeGledhill per averlo sottolineato!)
UserPrinciple.FindByIdentity
per vedere se l'ID utente passato esiste prima.
ContextOptions.Negotiate
.
Lo facciamo sulla nostra Intranet
Devi usare System.DirectoryServices;
Ecco le viscere del codice
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
è archiviato in LDAP in testo normale?
Close()
una using
variabile.
Diverse soluzioni qui presentate non hanno la possibilità di distinguere tra un utente / password errati e una password che deve essere modificata. Questo può essere fatto nel modo seguente:
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 la password dell'utente è errata o l'utente non esiste, l'errore conterrà
"8009030C: LdapErr: DSID-0C0904DC, commento: errore AcceptSecurityContext, dati 52e, v1db1",
se la password dell'utente deve essere modificata, conterrà
"8009030C: LdapErr: DSID-0C0904DC, commento: errore AcceptSecurityContext, dati 773, v1db1"
Il lexc.ServerErrorMessage
valore dei dati è una rappresentazione esadecimale del codice di errore Win32. Questi sono gli stessi codici di errore che verrebbero restituiti invocando altrimenti la chiamata API LogonUser Win32. L'elenco seguente riepiloga un intervallo di valori comuni con valori esadecimali e decimali:
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
soluzione molto semplice utilizzando 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;
}
l'accesso NativeObject è necessario per rilevare un utente / password errati
PrincipleContext
- che esiste solo in .NET 3.5. Ma se stai usando .NET 3.5 o più recente dovresti usarePrincipleContext
Sfortunatamente non esiste un modo "semplice" per controllare le credenziali di un utente su AD.
Con ogni metodo presentato finora, potresti ottenere un falso negativo: i crediti di un utente saranno validi, tuttavia AD restituirà false in determinate circostanze:
ActiveDirectory non ti consentirà di utilizzare LDAP per determinare se una password non è valida a causa del fatto che un utente deve modificare la password o se la loro password è scaduta.
Per determinare la modifica della password o la password scaduta, è possibile chiamare Win32: LogonUser () e controllare il codice di errore di Windows per le seguenti 2 costanti:
Probabilmente il modo più semplice è PInvoke LogonUser Win32 API.eg
Riferimento MSDN qui ...
Sicuramente utilizzare il tipo di accesso
LOGON32_LOGON_NETWORK (3)
Questo crea solo un token leggero, perfetto per i controlli AuthN. (altri tipi possono essere utilizzati per creare sessioni interattive ecc.)
LogonUser
API richiede che l'utente abbia Act come parte del privilegio del sistema operativo ; che non è qualcosa che gli utenti ottengono - e non qualcosa che desideri concedere a tutti gli utenti dell'organizzazione. ( msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx )
Una soluzione .Net completa consiste nell'utilizzare le classi dallo spazio dei nomi System.DirectoryServices. Consentono di interrogare direttamente un server AD. Ecco un piccolo esempio che farebbe questo:
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
Questo codice si collega direttamente al server AD, utilizzando le credenziali fornite. Se le credenziali non sono valide, searcher.FindOne () genererà un'eccezione. ErrorCode è quello corrispondente all'errore COM "nome utente / password non validi".
Non è necessario eseguire il codice come utente AD. In effetti, lo uso con successo per richiedere informazioni su un server AD, da un client esterno al dominio!
Ancora un'altra chiamata .NET per autenticare rapidamente le credenziali 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
}
}
Prova questo codice (NOTA: segnalato per non funzionare su 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
tranne che dovrai creare la tua eccezione personalizzata per "LogonException"
Se sei bloccato con .NET 2.0 e il codice gestito, ecco un altro modo che funziona con gli account locali e di 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;
}
L'autenticazione di Windows può non riuscire per vari motivi: un nome utente o una password errati, un account bloccato, una password scaduta e altro. Per distinguere tra questi errori, chiamare la funzione API LogonUser tramite P / Invoke e controllare il codice di errore se la funzione restituisce 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()
}
}
Esempio di utilizzo:
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 richiede una relazione di fiducia con il dominio con il quale stai convalidando.
La mia semplice funzione
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;
}
}
Ecco la mia soluzione di autenticazione completa per il tuo riferimento.
Innanzitutto, aggiungi i seguenti quattro riferimenti
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 = "";
}
}