Как проверить учетные записи Active Directory по протоколу LDAP + SSL?

Я пытаюсь использовать пространство имен .NET 3.5 System.DirectoryServices.AccountManagement, чтобы проверять учетные данные пользователя на нашем сервере LDAP Active Directory через SSL-шифрованное соединение LDAP. Здесь пример кода:

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:389", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
    return pc.ValidateCredentials(_username, _password);
}

Этот код отлично работает над незащищенным LDAP (порт 389), однако я бы предпочел не передавать комбинацию пользователя/пароля в виде открытого текста. Но когда я перехожу на LDAP + SSL (порт 636), я получаю следующее исключение:

System.DirectoryServices.Protocols.DirectoryOperationException: The server cannot handle directory requests.
  at System.DirectoryServices.Protocols.ErrorChecking.CheckAndSetLdapError(Int32 error)
  at System.DirectoryServices.Protocols.LdapSessionOptions.FastConcurrentBind()
  at System.DirectoryServices.AccountManagement.CredentialValidator.BindLdap(NetworkCredential creds, ContextOptions contextOptions)
  at System.DirectoryServices.AccountManagement.CredentialValidator.Validate(String userName, String password)
  at System.DirectoryServices.AccountManagement.PrincipalContext.ValidateCredentials(String userName, String password)
  at (my code)

Порт 636 работает для других действий, таких как поиск информации без пароля для этой записи LDAP/AD...

UserPrincipal.FindByIdentity(pc, IdentityType.SamAccountName, _username)

... поэтому я знаю, что это не моя настройка SSL-сервера LDAP, поскольку она работает через SSL для других поисков.

Кто-нибудь получил вызов ValidateCredentials(...) для работы через SSL? Можете ли вы объяснить, как это сделать? Или есть другой/лучший способ надежной проверки учетных данных AD/LDAP?

Ответ 1

Я смог проверить учетные данные, используя пространство имен System.DirectoryServices.Protocols, благодаря сотруднику. Здесь код:

// See http://support.microsoft.com/kb/218185 for full list of LDAP error codes
const int ldapErrorInvalidCredentials = 0x31;

const string server = "sd.example.com:636";
const string domain = "sd.example.com";

try
{
    using (var ldapConnection = new LdapConnection(server))
    {
        var networkCredential = new NetworkCredential(_username, _password, domain);
        ldapConnection.SessionOptions.SecureSocketLayer = true;
        ldapConnection.AuthType = AuthType.Negotiate;
        ldapConnection.Bind(networkCredential);
    }

    // If the bind succeeds, the credentials are valid
    return true;
}
catch (LdapException ldapException)
{
    // Invalid credentials throw an exception with a specific error code
    if (ldapException.ErrorCode.Equals(ldapErrorInvalidCredentials))
    {
        return false;
    }

    throw;
}

Я не в восторге от использования блока try/catch для контроля логики принятия решений, но это то, что работает.:/

Ответ 2

Возможно, это другой способ. Нет ничего необычного в проверке учетных данных. ContextOptions должен быть установлен правильно.

Значение по умолчанию:

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing

Добавить Ssl:

ContextOptions.Negotiate | ContextOptions.Signing | ContextOptions.Sealing | ContextOptions.SecureSocketLayer

ContextOptions.Negotiate или ContextOptions.SimpleBind требуется. Или независимо от того, что ваш сервер должен выполнить проверку подлинности. ContextOptions поддерживает только бит бит/бит.

Вы можете попробовать также настроить ContextOptions прямо таким образом в методе ValidateCredentials.

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate | ContextOptions.SecureSocketLayer))
{
    return pc.ValidateCredentials(_username, _password);
}

или

using (var pc = new PrincipalContext(ContextType.Domain, "sd.example.com:636", "DC=sd,DC=example,DC=com", ContextOptions.Negotiate))
{
    return pc.ValidateCredentials(_username, _password, ContextOptions.Negotiate | ContextOptions.SecureSocketLayer);
}

Ответ 3

Для меня метод ValidateCredentials работает отлично. Проблема, я обнаружил, была на сервере, на котором размещается AD (я использую AD LDS). Вам необходимо связать сертификат сервера с экземпляром AD. Поэтому, если ваш экземпляр был вызван "MyAD" (или ActiveDirectoryWebService), вам нужно было открыть MMC, щелкнуть в модуле "Сертификаты", выбрать "Учетная запись службы", а затем выбрать "MyAD" из списка. Оттуда вы можете добавить сертификат SSL в личный магазин MyAD. Это, наконец, привело к переходу на обработку SSL.

Я подозреваю, что я знаю метод LdapConnection и тот факт, что вы опустили функцию обратного вызова, что вы не проверяете сертификат своего сервера. Это грязная работа и ValidateCredentials делает это бесплатно. Наверное, это не большое дело, а дыра в безопасности все-таки меньше.

Ответ 4

Я знаю, что это старо, но для всех, кто сталкивается с этим снова:

PrincipalContext.ValidateCredentials(...) по умолчанию пытается открыть SSL-соединение (ldap_init(NULL, 636)), а затем LDAP_OPT_FAST_CONCURRENT_BIND параметр LDAP_OPT_FAST_CONCURRENT_BIND.

Однако если существует сертификат клиента (доверенный?), Соединение LDAP неявно привязано и быстрое связывание больше не может быть включено. PrincipalContext не рассматривает этот случай и не работает с непредвиденным исключением DirectoryOperationException.

Обход проблемы: Чтобы поддерживать SSL там, где это возможно, но иметь резервную копию, сначала вызовите ValidateCredentials(...) с параметрами по умолчанию (т.е. без параметров). Если это не удается с помощью DirectoryOperationException, попробуйте еще раз, указав ContextOptions (Negotiate | Sealing | Signing), что и делает ValidateCredentials внутренне для ожидаемого LdapException.