Réponses:
Si vous travaillez sur .NET 3.5 ou plus récent, vous pouvez utiliser l' System.DirectoryServices.AccountManagement
espace de noms et vérifier facilement vos informations d'identification:
// 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");
}
C'est simple, c'est fiable, c'est du code 100% C # géré de votre côté - que demander de plus? :-)
Lisez tout a propos de ça ici:
Mise à jour:
Comme indiqué dans cette autre question SO (et ses réponses) , il y a un problème avec cet appel qui peut renvoyer les True
anciens mots de passe d'un utilisateur. Soyez juste conscient de ce comportement et ne soyez pas trop surpris si cela se produit :-) (merci à @MikeGledhill de l'avoir signalé!)
UserPrinciple.FindByIdentity
pour voir si l'ID utilisateur transmis existe en premier.
ContextOptions.Negotiate
.
Nous le faisons sur notre intranet
Vous devez utiliser System.DirectoryServices;
Voici les tripes du code
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 stocké dans LDAP en texte brut?
Close()
une using
variable.
Plusieurs solutions présentées ici ne permettent pas de faire la différence entre un mauvais utilisateur / mot de passe et un mot de passe qui doit être changé. Cela peut se faire de la manière suivante:
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 le mot de passe des utilisateurs est incorrect ou si l'utilisateur n'existe pas, l'erreur contiendra
"8009030C: LdapErr: DSID-0C0904DC, commentaire: erreur AcceptSecurityContext, données 52e, v1db1",
si le mot de passe des utilisateurs doit être changé, il contiendra
"8009030C: LdapErr: DSID-0C0904DC, commentaire: erreur AcceptSecurityContext, données 773, v1db1"
La lexc.ServerErrorMessage
valeur des données est une représentation hexadécimale du code d'erreur Win32. Ce sont les mêmes codes d'erreur qui seraient retournés en invoquant autrement l'appel API Win32 LogonUser. La liste ci-dessous résume une plage de valeurs communes avec des valeurs hexadécimales et décimales:
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
etSystem.DirectoryServices.Protocols
solution très simple utilisant 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'accès NativeObject est requis pour détecter un mauvais utilisateur / mot de passe
PrincipleContext
- qui n'existe que dans .NET 3.5. Mais si vous utilisez .NET 3.5 ou plus récent, vous devez utiliserPrincipleContext
Malheureusement, il n'existe aucun moyen "simple" de vérifier les informations d'identification d'un utilisateur sur AD.
Avec chaque méthode présentée jusqu'à présent, vous pouvez obtenir un faux négatif: les crédits d'un utilisateur seront valides, mais AD retournera faux dans certaines circonstances:
ActiveDirectory ne vous permettra pas d'utiliser LDAP pour déterminer si un mot de passe n'est pas valide car un utilisateur doit changer de mot de passe ou si son mot de passe a expiré.
Pour déterminer le changement de mot de passe ou le mot de passe expiré, vous pouvez appeler Win32: LogonUser () et vérifier le code d'erreur Windows pour les 2 constantes suivantes:
Le moyen probablement le plus simple consiste à PInvoke LogonUser Win32 API.eg
Référence MSDN ici ...
Je veux vraiment utiliser le type d'ouverture de session
LOGON32_LOGON_NETWORK (3)
Cela crée un jeton léger uniquement - parfait pour les vérifications AuthN. (d'autres types peuvent être utilisés pour créer des sessions interactives, etc.)
LogonUser
API oblige l'utilisateur à avoir la loi dans le cadre du privilège du système d' exploitation; ce qui n'est pas quelque chose que les utilisateurs obtiennent - et non quelque chose que vous souhaitez accorder à chaque utilisateur de l'organisation. ( msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx )
Une solution .Net complète consiste à utiliser les classes de l'espace de noms System.DirectoryServices. Ils permettent d'interroger directement un serveur AD. Voici un petit échantillon qui ferait cela:
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
Ce code se connecte directement au serveur AD, à l'aide des informations d'identification fournies. Si les informations d'identification ne sont pas valides, searcher.FindOne () lèvera une exception. Le code d'erreur est celui correspondant à l'erreur COM "nom d'utilisateur / mot de passe invalide".
Vous n'avez pas besoin d'exécuter le code en tant qu'utilisateur AD. En fait, je l'utilise avec succès pour interroger des informations sur un serveur AD, à partir d'un client en dehors du domaine!
Encore un autre appel .NET pour authentifier rapidement les informations d'identification 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
}
}
Essayez ce code (REMARQUE: signalé comme ne fonctionnant pas sur 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
sauf que vous devrez créer votre propre exception personnalisée pour "LogonException"
Si vous êtes bloqué avec .NET 2.0 et le code managé, voici une autre méthode qui fonctionne avec les comptes locaux et de domaine:
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'authentification Windows peut échouer pour diverses raisons: un nom d'utilisateur ou un mot de passe incorrect, un compte verrouillé, un mot de passe expiré, etc. Pour faire la distinction entre ces erreurs, appelez la fonction API LogonUser via P / Invoke et vérifiez le code d'erreur si la fonction renvoie 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()
}
}
Exemple d'utilisation:
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;
}
}
Remarque: LogonUser nécessite une relation d'approbation avec le domaine contre lequel vous validez.
Ma fonction simple
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;
}
}
Voici ma solution d'authentification complète pour votre référence.
Tout d'abord, ajoutez les quatre références suivantes
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 = "";
}
}