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


526

Active Directoryに対してユーザー名とパスワードを検証するにはどうすればよいですか?ユーザー名とパスワードが正しいかどうかを確認したいだけです。

回答:


642

.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#で管理されたコードです-これ以上何を要求できますか?:-)

それについてここですべて読んでください:

更新:

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


36
私のドメインでは、pc.ValidateCredentials( "myuser"、 "mypassword"、ContextOptions.Negotiate)を指定する必要がありました。そうしないと、System.DirectoryServices.Protocols.DirectoryOperationException:サーバーがディレクトリ要求を処理できません。
Alex Peck

12
パスワードの有効期限が切れているか、アカウントが無効になっている場合、ValidateCredentialsはfalseを返します。残念なことに、それがfalseを返す理由はわかりません(これは、ユーザーをリダイレクトしてパスワードを変更するような賢明なことを実行できないことを意味するので、残念です)。
Chris J

64
また、「ゲスト」アカウントにも注意してください。ドメインレベルのゲストアカウントが有効になっている場合、存在しないユーザーに付与すると、ValidateCredentialsはtrueを返します。そのため、呼び出しUserPrinciple.FindByIdentityて、渡されたユーザーIDが最初に存在するかどうかを確認することができます。
Chris J

7
@AlexPeck:(私のように)これを行わなければならない理由は、.NETがデフォルトでLDAP + SSL、Kerberos、RPCのテクノロジーを使用しているためです。RPCがネットワークでオフになっている(良い!)と思います。明示的にを使用しない限り、Kerberosは実際には.NETで使用されませんContextOptions.Negotiate
Brett Veenstra、2011

5
ユーザーがActive Directoryパスワードを変更した場合、このコードは引き続き古いADパスワードを使用してユーザーを認証します。うん、本当に。:ここで読んだことがある stackoverflow.com/questions/8949501/...
マイクGledhill

70

これはイントラネットで行います

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

9
「パス」には何を入れますか?ドメインの名前は?サーバーの名前は?ドメインへのLDAPパス?サーバーへのLDAPパス?
Ian Boyd

3
回答1:いいえ、Webサービスとして実行しているため、メインWebアプリの複数の場所から呼び出すことができます。Answer2:パスにLDAP情報が含まれています... LDAP:// DC = domainname1、DC = domainname2、DC = com
DiningPhilanderer

3
これによりLDAPインジェクションが可能になると思われます。strAccountIdのかっこを必ずエスケープまたは削除してください
Brain2000

これはstrPassword、プレーンテキストでLDAPに保存されることを意味しますか?
Matt Kocaj

15
明示的に呼び出す必要があってはなりませんClose()using変数。
Nyerguds

62

ここで紹介するいくつかのソリューションには、間違ったユーザー/パスワードと、変更が必要なパスワードを区別する機能がありません。これは次の方法で実行できます。

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)

2
残念ながら、一部のADインストールはLDAPサブエラーコードを返さないため、このソリューションは機能しません。
セーレンモース

4
プロジェクトにいくつかの参照を追加することを忘れないでください:System.DirectoryServicesおよびSystem.DirectoryServices.Protocols
TomXP411 '

3
しかし、私の質問はこれです。LDAPサーバー名はどのように取得しますか?ポータブルアプリケーションを作成している場合、ユーザーがすべてのネットワーク上のADサーバーの名前を知っている、または指定する必要があるとは期待できません。
TomXP411 2013

1
特定のワークステーションへのログインに制限されているユーザーがいます。ログインしようとしているワークステーションを指定するにはどうすればよいですか?(たとえば、workstation1はデータ531で失敗し、workstation2は正常に動作します)
akohlsmith

1
十分なクレジットを獲得できていないと思います。これは完全に管理された方法であり、Win32 API呼び出しの問題がなく、「ユーザーはパスワードをリセットする必要がある」かどうかを判断できます。この方法には、低い評価率の原因となる抜け穴はありますか?うーん...
Lionet Chen

34

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


4
このコードは、承認チェックも実行しているため、不正です(ユーザーがActive Directory情報の読み取りを許可されているかどうかをチェックします)。ユーザー名とパスワードは有効ですが、ユーザーは情報を読み取ることができず、例外が発生します。つまり、有効なユーザー名とパスワードを設定しても、例外が発生します。
Ian Boyd

2
私は実際に.NET 3.5にのみ存在するネイティブの同等のものを要求している最中PrincipleContextです。ただし、.NET 3.5以降を使用している場合は、使用する必要がありますPrincipleContext
Ian Boyd

28

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

これまでに紹介したすべての方法では、偽陰性になる可能性があります。ユーザーの資格情報は有効ですが、特定の状況ではADは偽を返します。

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

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

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

  • ERROR_PASSWORD_MUST_CHANGE = 1907
  • ERROR_PASSWORD_EXPIRED = 1330

1
ExpiredとMust_Changeのdevinitionsをどこで取得したか尋ねてもらえますか?ここでしか見つかりませんでした:)
mabstrei


ありがとう。私の検証が常にfalseを返す方法を見つけようとしていました。これは、ユーザーがパスワードを変更する必要があるためです。
Decent Vicentin 2017

22

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

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

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

http://msdn.microsoft.com/en-us/library/aa378184.aspx

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

LOGON32_LOGON_NETWORK (3)

これにより、軽量トークンのみが作成されます-AuthNチェックに最適です。(他のタイプを使用してインタラクティブセッションなどを構築できます)


@Alanが指摘するように、LogonUser APIには、System.DirectoryServices呼び出し以外にも多くの便利な特性があります。
stephbu 2008

3
@cciotti:いいえ、それは間違っています。誰かを正しく認証する最良の方法は、LogonUserAPIを@stephbuの書き込みとして使用することです。この投稿で説明されている他のすべての方法は100%機能しません。ただし、LogonUserを呼び出すには、ドメインに参加している必要があると思います。
アラン

@Alanは、有効なドメインアカウントを渡してドメインに接続できるようにする必要がある資格情報を生成します。ただし、お使いのマシンが必ずしもドメインのメンバーである必要はないと確信しています。
stephbu 2009

2
LogonUserAPIは持っているユーザーを必要とオペレーティングシステムの一部として機能を privelage。これはユーザーが取得するものではなく、組織内のすべてのユーザーに付与するものではありません。(msdn.microsoft.com/en-us/library/aa378184(v=vs.85).aspx
イアン・ボイド

1
LogonUser は、support.microsoft.com / kb / 180548によると、Windows 2000以下のオペレーティングシステムの一部として機能するだけで済みます。Server2003以降では、見た目はきれいです。
Chris J

18

完全な.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サーバーの情報を照会するためにそれをうまく使用しています!


認証タイプはどうですか?上記のコードで忘れてしまったようです。:-)デフォルトでDirectoryEntry.AuthenticationTypeは保護された権利に設定されていますか?そのコードは、保護されていないLDAP(おそらく匿名またはなし)では機能しません。私はこれで正しいですか?
jerbersoft 2010年

ADサーバーを照会することの欠点は、ADサーバーを照会する権限があることです。資格情報は有効である可能性がありますが、ADにクエリを実行する権限がない場合、エラーが発生します。これがいわゆるFast Bindが作成された理由です。ユーザーが何かを実行することを許可せずに資格情報を検証します。
Ian Boyd

2
これにより、資格情報がチェックされる前に他の理由でCOMExceptionがスローされた場合に誰でもパスできるようになりますか?
Stefan Paul Noack 2017年

11

LDAP資格情報をすばやく認証するためのさらに別の.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
    }
}

これは私にとって有効な唯一のソリューションであり、PrincipalContextを使用しても機能しません。
ダニエル

安全なLDAP接続(別名LDAPS、ポート636を使用するPrincipalContextは無効)
Kiquenet

10

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


メソッドから情報を返すために例外処理を使用しないでください。「不明なユーザー名または不正なパスワード」は例外ではなく、LogonUserの標準的な動作です。ただfalseを返します。
Treb

はい...これは古いVB6ライブラリのポートでした... 2003年かそこらで書かれています...(.
Net

Windows 2000でこのコードを実行している場合はなりません作業(support.microsoft.com/kb/180548
イアン・ボイド

1
これを再考します。ログオンユーザーの予想される動作、その目的は、ユーザーをにログオンさせることです。それはそのタスクを実行するのに失敗した場合、それはIS例外。実際、メソッドはブール値ではなくvoidを返す必要があります。さらに、ブール値を返しただけの場合、メソッドのコンシューマーはユーザーに失敗の理由を通知する方法がありません。
Charles Bretana、

5

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

彼がスクリプトを起動したマシンのローカルアカウントでうまく機能します
eka808

ちなみに、このメソッドは、これを機能させるために必要ですpublic static SecureString ToSecureString(string PwString){char [] PasswordChars = PwString.ToCharArray(); SecureString Password = new SecureString(); foreach(PasswordCharsのchar c)Password.AppendChar(c); ProcessStartInfo foo = new ProcessStartInfo(); foo.Password = Password; foo.Passwordを返します。}
eka808

逆に、パスワードにはSecureStringを使用する必要があります。WPF PasswordBoxがサポートしています。
スティーブンドリュー

5

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("user@example.com", "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には、検証対象のドメインとの信頼関係が必要です。


なぜあなたの回答が最高投票数の回答よりも優れているのか説明できますか?
Mohammad Ali

1
@MohammadAli:資格情報の検証が失敗した理由(不正な資格情報、ロックされたアカウント、期限切れのパスワードなど)を知る必要がある場合は、LogonUser API関数が通知します。対照的に、PrincipalContext.ValidateCredentialsメソッド(marc_sの回答に関するコメントによる)は機能しません。これらすべてのケースでfalseを返します。一方、LogonUserはドメインとの信頼関係を必要としますが、PrincipalContext.ValidateCredentials(私は思う)は必要としません。
Michael Liu

2

私のシンプルな機能

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

1

ここに、参照用の完全な認証ソリューションを示します。

まず、次の4つの参照を追加します

 using System.DirectoryServices;
 using System.DirectoryServices.Protocols;
 using System.DirectoryServices.AccountManagement;
 using System.Net; 

private void AuthUser() { 


      try{
            string Uid = "USER_NAME";
            string Pass = "PASSWORD";
            if (Uid == "")
            {
                MessageBox.Show("Username cannot be null");
            }
            else if (Pass == "")
            {
                MessageBox.Show("Password cannot be null");
            }
            else
            {
                LdapConnection connection = new LdapConnection("YOUR DOMAIN");
                NetworkCredential credential = new NetworkCredential(Uid, Pass);
                connection.Credential = credential;
                connection.Bind();

                // after authenticate Loading user details to data table
                PrincipalContext ctx = new PrincipalContext(ContextType.Domain);
                UserPrincipal user = UserPrincipal.FindByIdentity(ctx, Uid);
                DirectoryEntry up_User = (DirectoryEntry)user.GetUnderlyingObject();
                DirectorySearcher deSearch = new DirectorySearcher(up_User);
                SearchResultCollection results = deSearch.FindAll();
                ResultPropertyCollection rpc = results[0].Properties;
                DataTable dt = new DataTable();
                DataRow toInsert = dt.NewRow();
                dt.Rows.InsertAt(toInsert, 0);

                foreach (string rp in rpc.PropertyNames)
                {
                    if (rpc[rp][0].ToString() != "System.Byte[]")
                    {
                        dt.Columns.Add(rp.ToString(), typeof(System.String));

                        foreach (DataRow row in dt.Rows)
                        {
                            row[rp.ToString()] = rpc[rp][0].ToString();
                        }

                    }  
                }
             //You can load data to grid view and see for reference only
                 dataGridView1.DataSource = dt;


            }
        } //Error Handling part
        catch (LdapException lexc)
        {
            String error = lexc.ServerErrorMessage;
            string pp = error.Substring(76, 4);
            string ppp = pp.Trim();

            if ("52e" == ppp)
            {
                MessageBox.Show("Invalid Username or password, contact ADA Team");
            }
            if ("775​" == ppp)
            {
                MessageBox.Show("User account locked, contact ADA Team");
            }
            if ("525​" == ppp)
            {
                MessageBox.Show("User not found, contact ADA Team");
            }
            if ("530" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this time, contact ADA Team");
            }
            if ("531" == ppp)
            {
                MessageBox.Show("Not permitted to logon at this workstation, contact ADA Team");
            }
            if ("532" == ppp)
            {
                MessageBox.Show("Password expired, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }
            if ("533​" == ppp)
            {
                MessageBox.Show("Account disabled, contact ADA Team");
            }



        } //common error handling
        catch (Exception exc)
        {
            MessageBox.Show("Invalid Username or password, contact ADA Team");

        }

        finally {
            tbUID.Text = "";
            tbPass.Text = "";

        }
    }
弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.