.NET Framework の LdapConnection を使って、LDAP でユーザー認証するサンプルコード。
このままだと、指定 DN 直下にいるユーザーでのみ認証できる。
DN を固定せず認証したい場合は、一旦 LDAP を検索可能かつ DN が判明しているアカウントでバインドしてユーザー名を検索したのち、そうして得た認証対象ユーザーの DN とパスワードを使ってバインドしてみる、って流れになる。
using System;
using System.DirectoryServices.Protocols;
using System.Text.RegularExpressions;
using System.Collections;
using System.Collections.Generic;
using System.Net;
namespace LdapTest
{
public static class LdapAuthnTest
{
/// <summary>
/// LDAP で認証する。
/// </summary>
/// <param name="userId">ユーザー名</param>
/// <param name="userPassword">ユーザーのパスワード</param>
/// <param name="userAttr">DN に指定されている属性 (多くの場合、CN)</param>
/// <param name="baseDN">ベース DN</param>
/// <param name="ldapServer">LDAP サーバーのホスト名または IP アドレス</param>
/// <param name="isTls">LDAPS にする場合 true。LDAP のままにする場合 false。</param>
/// <returns>認証成功でそのユーザーの <see cref="SearchResultEntry" />、認証失敗は null。</returns>
/// <exception cref="LdapException">LDAP 例外</exception>
public static SearchResultEntry LdapAuthn(string userId, string userPassword, string userAttr, string baseDN, string ldapServer, bool isTls)
{
// 認証情報が空の場合は即認証失敗
if (userId.Length < 1 || userPassword.Length < 1)
{
return null;
}
ldapServer = ServerPortSpecify(ldapServer, isTls);
LdapConnection ldapConnection = new LdapConnection(ldapServer) {
Credential = new NetworkCredential(userAttr + "=" + LdapEscape(userId) + "," + baseDN, userPassword),
AuthType = AuthType.Basic,
Timeout = new TimeSpan(0, 0, 10)
};
ldapConnection.SessionOptions.ProtocolVersion = 3;
ldapConnection.SessionOptions.SecureSocketLayer = isTls;
SearchResultEntry searchResultEntry = null;
try
{
// 認証したいユーザーでバインドしてみる
ldapConnection.Bind();
// バインドが通ったらユーザー情報を得る
SearchRequest searchRequest = new SearchRequest()
{
DistinguishedName = baseDN,
Filter = "(" + userAttr + "=" + userId + ")"
};
SearchResponse searchResponse = ldapConnection.SendRequest(searchRequest) as SearchResponse;
if (searchResponse.Entries.Count == 1)
{
foreach (SearchResultEntry item in searchResponse.Entries)
{
searchResultEntry = item;
break;
}
}
if (searchResultEntry == null)
{
// 念のため。この例外がスローされることはない。
throw new ArgumentOutOfRangeException(searchResponse.Entries.Count.ToString());
}
}
catch (LdapException e)
{
if (e.ErrorCode != 49) // エラー コード 49 は「認証失敗」
{
throw e;
}
else
{
System.Diagnostics.Debug.WriteLine(e.Message);
System.Diagnostics.Debug.WriteLine(e.ServerErrorMessage);
}
}
finally
{
if (ldapConnection != null)
{
try
{
ldapConnection.Dispose();
}
catch (Exception e)
{
System.Diagnostics.Debug.WriteLine(e);
}
}
}
return searchResultEntry;
}
/// <summary>
/// TCP ポート指定なし、かつ、LDAPS のときはポート指定「:636」を追加する。
/// </summary>
/// <param name="ldapServer">LDAP サーバー指定</param>
private static string ServerPortSpecify(string ldapServer, bool isTls)
{
if (isTls && !Regex.IsMatch(ldapServer, ":[1-9][0-9]*$")) return ldapServer + ":636";
else return ldapServer;
}
/// <summary>
/// 正常に処理できるよう、特殊文字をエスケープする。(RFC2253)
/// </summary>
/// <param name="ldapValue">エスケープ処理前の文字列</param>
/// <returns>エスケープ処理済みの文字列</returns>
static string LdapEscape(string ldapValue)
{
return "\"" + ldapValue.Replace("\\", "\\\\").Replace("\"", "\\\"") + "\"";
}
}
}