Было задано , и ответили за .NET , но теперь пришло время получить ответ для собственного кода Win32:
Как проверить имя пользователя и пароль Windows?
i задавал этот вопрос ранее для управляемого кода . Теперь пришло время для нативного решения.
Необходимо указать на подводные камни с некоторыми из наиболее часто предлагаемых решений:
Неверный метод 1. Запрос Active Directory с олицетворением
Многие люди предлагают что-то спросить в Active Directory . Если выдается исключение, то вы знаете, что учетные данные недействительны - как предложено в в этом вопросе о стеке .
Есть некоторые серьезные недостатки этого подхода однако:
Вы не только аутентифицируете учетную запись домена, но также делаете неявную проверку авторизации. То есть вы читаете свойства из AD, используя маркер олицетворения. Что если у действующей учетной записи нет прав на чтение из AD? По умолчанию все пользователи имеют доступ для чтения, но политики домена могут быть настроены на отключение разрешений на доступ для ограниченных учетных записей (и / или групп).
Привязка к AD имеет серьезные издержки, кэш схемы AD должен быть загружен на клиенте (кэш ADSI в поставщике ADSI, используемый DirectoryServices). Это и сеть, и сервер AD, потребляющий ресурсы, и он слишком дорог для такой простой операции, как аутентификация учетной записи пользователя.
Вы полагаетесь на ошибку исключения для неисключительного случая, и предполагаете, что это означает неправильное имя пользователя и пароль. Другие проблемы (например, сбой сети, сбой подключения AD, ошибка выделения памяти и т. Д.) Затем неверно интерпретируются как сбой аутентификации.
Использование класса DirectoryEntry
в .NET является примером неправильного способа проверки учетных данных:
Неверный метод 1a - .NET
DirectoryEntry entry = new DirectoryEntry("persuis", "iboyd", "Tr0ub4dor&3");
object nativeObject = entry.NativeObject;
Недопустимый метод 1b - .NET # 2
public static Boolean CheckADUserCredentials(String accountName, String password, String domain)
{
Boolean result;
using (DirectoryEntry entry = new DirectoryEntry("LDAP://" + domain, accountName, password))
{
using (DirectorySearcher searcher = new DirectorySearcher(entry))
{
String filter = String.Format("(&(objectCategory=user)(sAMAccountName={0}))", accountName);
searcher.Filter = filter;
try
{
SearchResult adsSearchResult = searcher.FindOne();
result = true;
}
catch (DirectoryServicesCOMException ex)
{
const int SEC_E_LOGON_DENIED = -2146893044; //0x8009030C;
if (ex.ExtendedError == SEC_E_LOGON_DENIED)
{
// Failed to authenticate.
result = false;
}
else
{
throw;
}
}
}
}
А также запросы Active Directory через соединение ADO:
Недопустимый метод 1c - собственный запрос
connectionString = "Provider=ADsDSOObject;
User ID=iboyd;Password=Tr0ub4dor&3;
Encrypt Password=True;Mode=Read;
Bind Flags=0;ADSI Flag=-2147483648';"
SELECT userAccountControl
FROM 'LDAP://persuis/DC=stackoverflow,DC=com'
WHERE objectClass='user' and sAMAccountName = 'iboyd'
Они оба не работают, даже если ваши учетные данные действительны , но у вас нет разрешения на просмотр записи каталога:
Неверный метод 2. LogonUser Win32 API
Другие предложили использовать функцию API LogonUser () . Это звучит хорошо, но, к сожалению, вызывающему пользователю иногда требуется разрешение, обычно предоставляемое только самой операционной системе:
Процесс, вызывающий LogonUser, требует
привилегия SE_TCB_NAME. Если
вызывающий процесс не имеет этого
привилегия, LogonUser не удается и
GetLastError возвращает
ERROR_PRIVILEGE_NOT_HELD.
В некоторых
случаи, процесс, который вызывает
LogonUser также должен иметь
Привилегия SE_CHANGE_NOTIFY_NAME
включен; в противном случае LogonUser завершается ошибкой
и GetLastError возвращает
ERROR_ACCESS_DENIED. Эта привилегия
не требуется для локальной системы
учетная запись или учетные записи, которые являются членами
из группы администраторов. От
по умолчанию SE_CHANGE_NOTIFY_NAME -
включен для всех пользователей, но некоторые
администраторы могут отключить его для
все.
Раздача " Act как части операционной системы " privelage - это не то, что вы хотите делать волей-неволей - как Microsoft указывает в статье базы знаний :
... процесс, который вызывает
LogonUser должен иметь SE_TCB_NAME
привилегия (в диспетчере пользователей это
« Закон как часть операционной
Система"справа). SE_TCB_NAME
привилегия очень мощная и
не должно быть предоставлено любому произвольному пользователю только для того, чтобы он мог
запустить приложение , которое должно
проверить учетные данные.
Кроме того, вызов LogonUser () не будет выполнен, если указан пустой пароль.
Допустимый метод .NET 3.5 - PrincipalContext
Существует метод проверки, доступный только в .NET 3.5 и новее, который позволяет аутентификацию пользователя без проверки прав доступа:
// create a "principal context" - e.g. your domain (could be machine, too)
using(PrincipalContext pc = new PrincipalContext(ContextType.Domain, "stackoverflow.com"))
{
// validate the credentials
bool isValid = pc.ValidateCredentials("iboyd", "Tr0ub4dor&3")
}
К сожалению, этот код доступен только в .NET 3.5 и более поздних версиях.
Пора найти эквивалент native .