web-dev-qa-db-ja.com

Active Directoryに対してユーザー名とパスワードを検証しますか?

Active Directoryに対してユーザー名とパスワードを検証する方法を教えてください。ユーザー名とパスワードが正しいかどうかを確認したいだけです。

493
Scott

.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#管理されたコードです。 :-)

それについてのすべてをここに読んでください。

更新:

this other SO question(およびその回答) で説明されているように、この呼び出しには、ユーザーの古いパスワードに対してTrueを返す可能性があるという問題があります。もしこれが起こったとしても、あまりにも驚かないでください:-)(これを指摘してくれた@MikeGledhillに感謝します!)

610
marc_s

私達は私達のイントラネットでこれをします

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();
        }
    }
}
67

ここに提示されたいくつかの解決策は間違ったユーザ/パスワードと、変更される必要があるパスワードとを区別する能力を欠いています。これは次のようにして行うことができます。

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エラーコードの16進表現です。これらは、それ以外の場合はWin32 LogonUser API呼び出しを呼び出すことによって返されるのと同じエラーコードです。以下のリストは、16進値と10進値を含む一般的な値の範囲を要約したものです。

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)
54
Søren Mors

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アクセスが必要です

33
Steven A. Lowe

残念ながら、ADでユーザーの資格情報を確認するための "簡単な"方法はありません。

これまでに示したすべての方法で、偽陰性になる可能性があります。ユーザーのクレジットは有効ですが、特定の状況下ではADはfalseを返します。

  • ユーザーは次回ログオン時にパスワードを変更する必要があります。
  • ユーザーのパスワードが期限切れです。

ActiveDirectoryでは、ユーザーがパスワードを変更する必要があるため、またはパスワードの有効期限が切れているために、LDAPを使用してパスワードが無効かどうかを判断することはできません。

パスワードの変更またはパスワードの有効期限切れを確認するには、Win32:LogonUser()を呼び出して、Windowsエラーコードで次の2つの定数を確認します。

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330
28
Alan

おそらく最も簡単な方法は、LogonUser Win32 API.eをPInvokeすることです。

http://www.pinvoke.net/default.aspx/advapi32/LogonUser.html

MSDNリファレンスはこちら...

http://msdn.Microsoft.com/ja-jp/library/aa378184.aspx

間違いなくログオンタイプを使いたい

LOGON32_LOGON_NETWORK (3)

これは軽量のトークンのみを作成します - 認証チェックに最適です。 (他のタイプでインタラクティブセッションなどを構築することができます)

21
stephbu

完全な.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サーバー上の情報を照会するのに使用することに成功しました。

18

LDAP認証情報を迅速に認証するためのもう1つの.NET呼び出し。

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
    }
}
11
palswim

このコードを試してください(注: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"には独自のカスタム例外を作成する必要があります。

10
Charles Bretana

.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;
}   
5
chauwel

誤ったユーザー名またはパスワード、ロックされたアカウント、期限切れのパスワードなど、Windows認証はさまざまな理由で失敗する可能性があります。これらのエラーを区別するには、P/Invokeで LogonUser API関数を呼び出し、関数が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("[email protected]", "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には、検証しているドメインとの信頼関係が必要です。

2
Michael Liu

私の簡単な機能

 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;
        }
    }
2