2015-02-19 3 views
7

У нас есть приложение mvc, использующее Active Directory для аутентификации наших пользователей. Мы используя System.DirectoryServices и используя PricipalContext для аутентификации:Вызов NetValidatePasswordPolicy из C# всегда возвращает Пароль должен быть изменен

_principalContext.ValidateCredentials(userName, pass, ContextOptions.SimpleBind);

Однако этот метод возвращает только логическое значение, и мы хотим, чтобы вернуть лучшие сообщения или даже перенаправить пользователя на экран сброса пароля для случаев, как:

  1. Пользователь заблокирован из своего аккаунта.
  2. Срок действия пароля пользователя истек.
  3. Пользователь должен изменить пароль при следующем входе в систему.

Таким образом, если пользователь не войти мы называем NetValidatePasswordPolicy понять, почему пользователь не смог войти. Это, кажется, работает хорошо, но мы поняли, что этот метод не только возвращение NET_API_STATUS.NERR_PasswordMustChange независимо от того, в каком состоянии из Пользователь Active Directory был.

Единственный пример, который я нашел с этой же проблемой, - это плагин Sublime Speech here. Код, я использую выглядит следующим образом:

var outputPointer = IntPtr.Zero; 
var inputArgs = new NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG { PasswordMatched = false, UserAccountName = username }; 
inputArgs.ClearPassword = Marshal.StringToBSTR(password); 

var inputPointer = IntPtr.Zero; 
inputPointer = Marshal.AllocHGlobal(Marshal.SizeOf(inputArgs)); 
Marshal.StructureToPtr(inputArgs, inputPointer, false); 

using (new ComImpersonator(adImpersonatingUserName, adImpersonatingDomainName, adImpersonatingPassword)) 
{ 
    var status = NetValidatePasswordPolicy(serverName, IntPtr.Zero, NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication, inputPointer, ref outputPointer); 

    if (status == NET_API_STATUS.NERR_Success) 
    { 
     var outputArgs = (NET_VALIDATE_OUTPUT_ARG)Marshal.PtrToStructure(outputPointer, typeof(NET_VALIDATE_OUTPUT_ARG)); 
     return outputArgs.ValidationStatus; 
    } 
    else 
    { 
     //fail 
    } 
} 

код всегда удается так, почему это значение outputArgs.ValidationStatus тот же результат каждый раз, независимо от состояния пользователя Active Directory?

+0

Вы пробовали GetLastError() после ValidateCredentials()? то вы можете использовать код ошибки для определения сообщения, например, 1907 «пользователь должен сбросить пароль», 1330 «истек срок действия пароля» и т. д. ... другой вариант, о котором я могу думать, - [Logon] (http: // www.pinvoke.net/default.aspx/advapi32/LogonUser.html) вместо ValidateCredentials, а затем GetLastError() –

+0

Я просмотрел исходный код ValidateCredentials(), метод использует LADP, если есть код ошибки, метод будет генерировать исключение, а затем поймать исключение и вернуть false. поэтому GetLastError, вероятно, будет работать. Я также видел, что есть специальные коды ошибок для активного каталога. –

+0

Мы не хотели использовать метод LogonUser, потому что мы используем службы каталогов для входа в систему. Если пользователь ошибочно вводит свой пароль, мы попытаемся выполнить вход в систему, выйти из системы, а затем войти в систему, чтобы узнать, почему это не удалось. Это дало бы две неудачные попытки входа в систему для каждой попытки пользователя. Я посмотрю на GetLastError(), но кажется странным, что ValidateCredentials будет возвращать 1907 каждый раз, а затем заставит разработчика посмотреть GetLastError(), чтобы получить фактический код. – johnnywhoop

ответ

6

сломит ответ на этот вопрос в три различные секции:

  1. Текущая проблема с вашей методологией
  2. вопросами с рекомендованными Solutions как онлайн, так и в этой теме
  3. Решение

Текущая проблема с вашей методологией.

NetValidatePasswordPolicy требует своего параметра InputArgs взять в указатель на структуру, а структура вы передаете в зависимости от ValidationType вы как раз проходя. В этом случае, вы передаете NET_VALIDATE_PASSWORD_TYPE.NetValidateAuthentication, что требует InputArgs из NET_VALIDATE_AUTHENTICATION_INPUT_ARG но вы передаете указатель на NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG.

Кроме того, вы пытаетесь присвоить «тип currentPassword» ценности для NET_VALIDATE_PASSWORD_CHANGE_INPUT_ARG структуры.

Однако есть большая фундаментальная proble к использованию NetValidatePasswordPolicy и что это то, что вы пытаетесь использовать эту функцию для проверки паролей в Active Directory, но это не то, что он используется для. NetValidatePasswordPolicy используется, чтобы позволить приложения для проверки по базе данных аутентификации, предоставляемых приложением.

Там еще информации о NetValidatePasswordPolicyhere.

Вопросы рекомендуемого решения как в Интернете, так и в этой теме

Различные статьи в Интернете рекомендуется использовать функцию LogonUser найти в AdvApi32.dll, но эта реализация носит свой собственный набор вопросов:

Первый является что LogonUser проверяет локальный кеш, а это означает, что вы не получите мгновенную точную информацию об учетной записи, если только вы не используете режим «Сеть».

Во-вторых, использование LogonUser в веб-приложении, на мой взгляд, немного хакерское, так как оно предназначено для настольных приложений, работающих на клиентских компьютерах. Однако, учитывая ограничения, предоставленные Microsoft, если LogonUser дает желаемые результаты, я не понимаю, почему его не следует использовать - запрет на проблемы кэширования.

Другая проблема с LogonUser заключается в том, насколько хорошо он работает в вашем случае использования, зависит от того, как настроен ваш сервер, например: Существуют определенные разрешения, которые необходимо включить в домене, который вы аутентифицируете, для работы в режиме «Сетевой».

Дополнительная информация о LogonUserhere.

Кроме того, не следует использовать, вместо этого следует использовать GetLastWin32Error(), так как небезопасно использовать .

Дополнительная информация о GetLastWin32Error()here.

Решение.

Чтобы получить точный код ошибки в Active Directory, без каких-либо проблем с кешированием и прямо из служб каталогов, это то, что нужно сделать: полагаться на COMException, возвращающийся из AD, когда есть проблема с учетной записью, потому что, в конечном счете, ошибки - это то, что вы ищете.

Во-первых, вот как вызвать ошибку из Active Directory аутентификации текущего имени пользователя и пароля:

public LdapBindAuthenticationErrors AuthenticateUser(string domain, string username, string password, string ouString) 
    { 
     // The path (ouString) should not include the user in the directory, otherwise this will always return true 
     DirectoryEntry entry = new DirectoryEntry(ouString, username, password); 

     try 
     { 
      // Bind to the native object, this forces authentication. 
      var obj = entry.NativeObject; 
      var search = new DirectorySearcher(entry) { Filter = string.Format("({0}={1})", ActiveDirectoryStringConstants.SamAccountName, username) }; 
      search.PropertiesToLoad.Add("cn"); 
      SearchResult result = search.FindOne(); 

      if (result != null) 
      { 
       return LdapBindAuthenticationErrors.OK; 
      } 
     } 
     catch (DirectoryServicesCOMException c) 
     { 
      LdapBindAuthenticationErrors ldapBindAuthenticationError = -1; 
        // These LDAP bind error codes are found in the "data" piece (string) of the extended error message we are evaluating, so we use regex to pull that string 
        if (Regex.Match(c.ExtendedErrorMessage, @" data (?<ldapBindAuthenticationError>[a-f0-9]+),").Success) 
        { 
         string errorHexadecimal = match.Groups["ldapBindAuthenticationError"].Value; 
         ldapBindAuthenticationError = (LdapBindAuthenticationErrors)Convert.ToInt32(errorHexadecimal , 16); 
         return ldapBindAuthenticationError; 
        } 
      catch (Exception e) 
      { 
       throw; 
      } 
     } 

     return LdapBindAuthenticationErrors.ERROR_LOGON_FAILURE; 
    } 

И это ваши «LdapBindAuthenticationErrors», вы можете найти больше в MSDN, here.

internal enum LdapBindAuthenticationErrors 
    {    
     OK = 0 
     ERROR_INVALID_PASSWORD = 0x56, 
     ERROR_PASSWORD_RESTRICTION = 0x52D, 
     ERROR_LOGON_FAILURE = 0x52e, 
     ERROR_ACCOUNT_RESTRICTION = 0x52f, 
     ERROR_INVALID_LOGON_HOURS = 0x530, 
     ERROR_PASSWORD_EXPIRED = 0x532, 
     ERROR_ACCOUNT_DISABLED = 0x533, 
     ERROR_ACCOUNT_EXPIRED = 0x701, 
     ERROR_PASSWORD_MUST_CHANGE = 0x773, 
     ERROR_ACCOUNT_LOCKED_OUT = 0x775 
    } 

Затем вы можете использовать возвращаемый тип этого Enum и делать то, что вам нужно с ним в вашем контроллере. Важно отметить, что вы ищете фрагмент «данных» строки в «Расширенном сообщении об ошибке» вашего COMException, потому что в нем содержится всемогущий код ошибки, который вы ищете.

Удачи, и я надеюсь, что это поможет. Я тестировал его, и он отлично работает для меня.

Смежные вопросы