LoginSignup
0
3

More than 3 years have passed since last update.

LDAP でユーザー認証

Last updated at Posted at 2021-01-29

.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("\"", "\\\"") + "\"";
        }
    }
}
0
3
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
0
3