Active Directory に対してユーザー名とパスワードを検証するにはどうすればよいですか? ユーザー名とパスワードが正しいかどうかを確認したいだけです。
14 に答える
.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# で管理されたコードです。これ以上何を求めることができますか? :-)
ここでそれについてすべて読んでください:
- .NET Framework 3.5 でのディレクトリ セキュリティ プリンシパルの管理
- System.DirectoryServices.AccountManagement に関する MSDN ドキュメント
アップデート:
this other SO question (およびその回答)で概説されているように、この呼び出しには問題がTrue
あり、ユーザーの古いパスワードが返される可能性があります。この動作に注意してください。これが発生してもあまり驚かないでください :-) (これを指摘してくれた @MikeGledhill に感謝します!)
これはイントラネットで行います
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();
}
}
}
ここで紹介するいくつかのソリューションには、間違ったユーザー/パスワードと変更が必要なパスワードを区別する機能がありません。これは、次の方法で行うことができます。
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)
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 アクセスが必要です
残念ながら、ADでユーザーの資格情報を確認する「簡単な」方法はありません。
これまでに提示されたすべてのメソッドで、偽陰性が発生する可能性があります。ユーザーの資格情報は有効ですが、特定の状況ではADは偽を返します。
- ユーザーは、次回のログオン時にパスワードを変更する必要があります。
- ユーザーのパスワードの有効期限が切れています。
ActiveDirectoryでは、LDAPを使用して、ユーザーがパスワードを変更する必要があるためにパスワードが無効であるかどうか、またはパスワードの有効期限が切れているかどうかを判断できません。
パスワードの変更またはパスワードの有効期限が切れているかどうかを判断するには、Win32:LogonUser()を呼び出し、Windowsのエラーコードで次の2つの定数を確認します。
- ERROR_PASSWORD_MUST_CHANGE = 1907
- ERROR_PASSWORD_EXPIRED = 1330
おそらく最も簡単な方法は PInvoke LogonUser Win32 API.eg です。
MSDNリファレンスはこちら...
どうしてもログオンタイプを使いたい
LOGON32_LOGON_NETWORK (3)
これにより、軽量のトークンのみが作成されます - AuthN チェックに最適です。(他のタイプは、インタラクティブなセッションなどを構築するために使用できます)
完全な .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 サーバーの情報を照会するために、これを使用することに成功しました。
Yet another .NET call to quickly authenticate LDAP credentials:
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
}
}
このコードを試してください (注: 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」に対して独自のカスタム例外を作成する必要があります
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 には、検証対象のドメインとの信頼関係が必要です。
.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;
}
私の簡単な機能
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;
}
}