Он был спросил, а ответил для .NET, но теперь пришло время получить ответ для собственного кода Win32:
Как проверить имя пользователя и пароль Windows?
i задал этот вопрос раньше для управляемого кода. Теперь пришло время для собственного решения.
Нужно указать на подводные камни с некоторыми из наиболее распространенных решений:
Недействительный метод 1. Запрос Active Directory с олицетворением
Многие люди предлагают запрашивать Active Directory. Если выбрано исключение, то вы знаете, что учетные данные недействительны - как это предлагается в этом вопросе о стеке_потока.
Есть некоторые серьезные недостатки этого подхода:
Вы не только аутентифицируете учетную запись домена, но также выполняете неявную проверку авторизации. То есть вы читаете свойства из AD, используя маркер олицетворения. Что делать, если в противном случае действительная учетная запись не имеет права читать из AD? По умолчанию все пользователи имеют доступ на чтение, но для политик домена можно отключить разрешения доступа для ограниченных учетных записей (или групп).
Привязка к AD имеет серьезные накладные расходы, кеш AD-схемы должен быть загружен на клиенте (кеш ADSI в поставщике ADSI, используемом DirectoryServices). Это как сетевой, так и серверы AD, потребляющие ресурсы - и слишком дороги для простой операции, такой как аутентификация учетной записи пользователя.
Вы полагаетесь на отказ исключения для случая, отличного от исключительных, и считаете, что это означает неправильное имя пользователя и пароль. Другие проблемы (например, сбой в сети, отказ подключения AD, ошибка выделения памяти и т.д.) Затем неправильно интерпретируются как сбой аутентификации.
Использование класса DirectoryEntry
- это пример неправильного способа проверки учетных данных:
Недействительный метод 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
Другие предложили использовать LogonUser() API. Это звучит неплохо, но, к сожалению, вызывающему пользователю иногда требуется разрешение, доступное только для самой операционной системы:
Для вызова процесса LogonUser требуется привилегия SE_TCB_NAME. Если вызывающий процесс не имеет этого привилегии, LogonUser выходит из строя и GetLastError возвращает ERROR_PRIVILEGE_NOT_HELD.
В некоторых случаях, процесс, который вызывает LogonUser также должен иметь SE_CHANGE_NOTIFY_NAME привилегия включен; в противном случае LogonUser не работает и GetLastError возвращается ERROR_ACCESS_DENIED. Эта привилегия не требуется для локальной системы учетные записи или учетные записи, которые являются членами группы администраторов. От по умолчанию, SE_CHANGE_NOTIFY_NAME для всех пользователей, но некоторые администраторы могут отключить его для каждый.
Отправка "Акта как части операционной системы" - это не то, что вы хотите делать волей-неволей - как указывает Microsoft в статья базы знаний:
... процесс, вызывающий LogonUser должен иметь SE_TCB_NAME привилегии (в User Manager это "Закон как часть действующего Система" справа). 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 и более поздних версиях.
Пришло время найти родной эквивалент.