Validate a username and password against Active Directory?
Как я могу проверить имя пользователя и пароль в Active Directory? I simply want to check if a username and password are correct.
15 ответов
Если вы работаете в.NET 3.5 или новее, вы можете использовать System.DirectoryServices.AccountManagement
Пространство имен и легко проверить свои учетные данные:
// 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");
}
Это просто, это надежно, это 100% C# управляемый код с вашей стороны - что еще вы можете попросить?:-)
Прочтите все об этом здесь:
- Управление принципами безопасности каталогов в.NET Framework 3.5
- Документы MSDN на System.DirectoryServices.AccountManagement
Обновить:
Как указано в этом другом вопросе SO (и его ответах), существует проблема с возвращением этого вызова True
для старых паролей пользователя. Просто помните об этом и не удивляйтесь, если это произойдет:-) (спасибо @MikeGledhill за то, что указал на это!)
В некоторых решениях, представленных здесь, отсутствует возможность различать неправильный пользователь / пароль и пароль, который необходимо изменить. Это можно сделать следующим образом:
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);
}
}
}
}
Если пароль пользователя неверен или пользователь не существует, ошибка будет содержать
"8009030C: LdapErr: DSID-0C0904DC, комментарий: ошибка AcceptSecurityContext, данные 52e, v1db1",
если необходимо изменить пароль пользователя, он будет содержать
"8009030C: LdapErr: DSID-0C0904DC, комментарий: ошибка AcceptSecurityContext, данные 773, v1db1"
lexc.ServerErrorMessage
значение данных представляет собой шестнадцатеричное представление кода ошибки Win32. Это те же самые коды ошибок, которые были бы возвращены, если бы в противном случае был вызван вызов Win32 LogonUser API. Список ниже суммирует диапазон общих значений с шестнадцатеричными и десятичными значениями:
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;
Вот кишки кода
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();
}
}
}
Очень простое решение с использованием 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;
}
доступ к NativeObject необходим для обнаружения неверного пользователя / пароля
К сожалению, не существует "простого" способа проверить учетные данные пользователей в AD.
С каждым представленным методом вы можете получить ложноотрицательный результат: кредиты пользователя будут действительны, однако AD вернет ложь при определенных обстоятельствах:
- Пользователь должен сменить пароль при следующем входе в систему.
- Срок действия пароля пользователя истек.
ActiveDirectory не позволит вам использовать LDAP для определения того, является ли пароль недействительным из-за того, что пользователь должен сменить пароль или срок его действия истек.
Чтобы определить изменение пароля или срок его действия истек, вы можете вызвать Win32:LogonUser() и проверить код ошибки Windows на наличие следующих 2 констант:
- ERROR_PASSWORD_MUST_CHANGE = 1907
- ERROR_PASSWORD_EXPIRED = 1330
Вероятно, самый простой способ это PInvoke LogonUser Win32 API.eg
Ссылка MSDN здесь...
Определенно хотите использовать тип входа
LOGON32_LOGON_NETWORK (3)
Это создает только легкий токен - идеально подходит для проверок AuthN. (другие типы могут использоваться для создания интерактивных сессий и т. д.)
Полное решение.Net заключается в использовании классов из пространства имен System.DirectoryServices. Они позволяют запрашивать сервер AD напрямую. Вот небольшой пример, который бы сделал это:
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
Этот код напрямую подключается к серверу AD, используя предоставленные учетные данные. Если учетные данные недействительны, Searcher.FindOne() сгенерирует исключение. ErrorCode - это код, соответствующий ошибке COM "неверное имя пользователя / пароль".
Вам не нужно запускать код как пользователь AD. Фактически, я успешно использую его для запроса информации на сервере AD от клиента за пределами домена!
Еще один вызов.NET для быстрой аутентификации учетных данных 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
}
}
Попробуйте этот код (ПРИМЕЧАНИЕ. Сообщается, что он не работает на 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
за исключением того, что вам нужно создать собственное исключение для "LogonException"
Проверка подлинности Windows может завершиться неудачей по разным причинам: неверное имя пользователя или пароль, заблокированная учетная запись, пароль с истекшим сроком действия и многое другое. Чтобы различать эти ошибки, вызовите функцию API LogonUser через P/Invoke и проверьте код ошибки, если функция возвращает 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()
}
}
Пример использования:
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;
}
}
Примечание. LogonUser требует доверительных отношений с доменом, с которым вы проверяете.
Если вы застряли с.NET 2.0 и управляемым кодом, вот еще один способ, который работает с локальными и доменными учетными записями:
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;
}
Моя простая функция
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;
}
}
Для меня оба нижеприведенных варианта работали, убедитесь, что ваш домен указан с LDAP:// в начале
//"LDAP://" + domainName
private void btnValidate_Click(object sender, RoutedEventArgs e)
{
try
{
DirectoryEntry de = new DirectoryEntry(txtDomainName.Text, txtUsername.Text, txtPassword.Text);
DirectorySearcher dsearch = new DirectorySearcher(de);
SearchResult results = null;
results = dsearch.FindOne();
MessageBox.Show("Validation Success.");
}
catch (LdapException ex)
{
MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
}
catch (Exception ex)
{
MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
}
}
private void btnValidate2_Click(object sender, RoutedEventArgs e)
{
try
{
LdapConnection lcon = new LdapConnection(new LdapDirectoryIdentifier((string)null, false, false));
NetworkCredential nc = new NetworkCredential(txtUsername.Text,
txtPassword.Text, txtDomainName.Text);
lcon.Credential = nc;
lcon.AuthType = AuthType.Negotiate;
lcon.Bind(nc);
MessageBox.Show("Validation Success.");
}
catch (LdapException ex)
{
MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
}
catch (Exception ex)
{
MessageBox.Show($"Validation Failure. {ex.GetBaseException().Message}");
}
}
Вот мое полное решение для аутентификации для вашей справки.
Сначала добавьте следующие четыре ссылки
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 = "";
}
}
Я использую эту процедуру как DLL для входа в другое приложение, которое мы разработали...
(В настоящее время мы используем это с OpenEdge Progress)
public static string AzureLogin(string user, string password) {
string status;
try {
new DirectorySearcher(new DirectoryEntry("LDAP://yourdomain.com", user, password) {
AuthenticationType = AuthenticationTypes.Secure,
Username = user,
Password = password
}) {
Filter = "(objectclass=user)"
}.FindOne().Properties["displayname"][0].ToString();
status = $"SUCCESS - User {user} has logged in.";
} catch(System.Exception e) {
status = $"ERROR - While logging in: {e}";
}
return status;
}