Macで .NET Core 3.1のアプリを開発する中、ユーザーがActiveDirectory (以降、AD)に参加していない環境で、ADサーバーを使ったLDAP認証をする機会があったため、メモを残します。
目次
環境
また、ADまわりは以下の環境です。
項目 | 値 |
---|---|
ADサーバ | Windows Server 2016 |
ドメインの機能 | 2016 |
Macユーザ | ドメインに不参加、ただしADユーザのIDとパスワードは分かる |
Mac | ドメインのコンピュータとして参加済、ただしドメインのComputersへ登録のみ |
AD - Mac間接続 | 直接 (Proxyなし) |
ADドメイン名 | sub.example.co.jp |
ADユーザ情報はこんな感じ
項目 | 値 |
---|---|
ユーザーログオン名 | foo_user@sub.example.co.jp |
ユーザーログオン名(Windows2000以前) | foo_user |
姓 | foo |
名 | bar |
表示名 | foo bar |
そもそも、なぜWindows認証ではないのか
ADを使った認証というと、まずはWindows認証が思い浮かびます。
ASP.NET Core 3.1でWindows認証が可能な方法を調べたところ、以下の3つでした。
ASP.NET Core での Windows 認証を構成します。 | Microsoft Docs
このうち、Mac上で ASP.NET Coreをホストして使えるのは、 Kestrel
だけでした。
とはいえ、Kestrelは機能不足なところがありそうなので、できれば避けたいと考えました。
ASP.NET Coreを動かすためのIISの構築方法 - Qiita
他に方法がないか考えたところ、以前PythonやRubyでLDAP認証を使っていたことを思い出しました。
- Django + LDAP3で、ActiveDirectoryのLDAP認証によるログインとログアウトを試してみた - メモ的な思考的な
- Ruby + ActiveLdapでActiveDirectoryにいるユーザーの所属グループを取得する - メモ的な思考的な
そこで、 .NET Coreで使えるLDAP認証ライブラリを探したところ、 dsbenghe/Novell.Directory.Ldap.NETStandard
がありました。
dsbenghe/Novell.Directory.Ldap.NETStandard: LDAP client library for .NET Standard 1.3 and up - works with any LDAP protocol compatible directory server (including Microsoft Active Directory).
最近リリースされたばかり(2020年1月下旬)だったので、以下を参考に試してみました。
.NET Core LDAP authentication
準備
ソリューションとプロジェクトの作成
今回は、コンソールアプリケーションで作ります。
$ dotnet new sln -n LdapAuth $ dotnet new console -o LdapAuthConsole $ dotnet sln add ./LdapAuthConsole
NuGetでインストール
Novell.Directory.Ldap.NETStandard
をインストールします。
$ cd LdapAuthConsole/ $ dotnet add package Novell.Directory.Ldap.NETStandard
使い方の流れ
コネクションの作成
まずはLDAPコネクションを作成します。
using (var connection = new LdapConnection { SecureSocketLayer = false })
Bind
続いてBindします。
この時、ADのユーザIDとパスワードが必要になります。Macのユーザとは異なっていて問題ないです。
connection.Connect(ipAddress, port); connection.Bind(userDn, password);
接続が成功した場合は、 connection.Bound
が true
になります。
ADの検索
検索するには、 connection.Search()
を使います。
引数は5つあります。
第一引数 (searchBase)
どこを探すかを指定します。
今回はドメインすべてを検索するため、 dc=sub, dc=example, dc=co, dc=jp
を指定します。
なお、dcの数を動的に指定したかったため、以下のような関数を用意しました。
これで、 sub.example.co.jp
が dc=sub, dc=example, dc=co, dc=jp
になります。
static string ConvertDomain(string domain) { return domain.Split(".") .Select(s => $"dc={s}") .Aggregate((result, element) => $"{result}, {element}"); }
第二引数 (LdapConnectionクラス)
どの範囲で検索するかを指定します。
今回は、ドメイン内全てを検索するため、 LdapConnection.ScopeSub
とします*1。
第三引数 (フィルタ)
絞り込み条件を指定します。
今回は、ADユーザのログインIDをキーにデータを取得するため、 SAMAccountName
を使います。
またあいまい検索もしたいため、 *
を使った
$"(SAMAccountName=*{userName}*)"
を指定します。
第四引数 (取得したい項目)
ADから取得したい項目をここで指定します。
どのような値を指定できるかは以下が参考になりました。
- Name Attributes - Hilltop Lab
- 2.4.3 Active Directoryとの連携 - JP1 Version 9 JP1/IT Desktop Management 導入・設計ガイド
今回は以下を指定しました。
new [] { "displayName", // 表示名 "cn", // 表示名と同じ "sn", // 姓 "givenName", // 名 "userPrincipalName", // ユーザーログオン名 "sAMAccountName", // ユーザーログオン名(Windows 2000以前) "description" // 説明 },
主な引数はこんな感じです。
値の取得
Search()で取得した値は、 GetAttribute()
を使って取得できます。
var user = result.Next();
var displayName = user.GetAttribute("displayName").StringValue;
ソースコード全体
ここまでをまとめると以下の感じです。
using System; using System.Linq; using Novell.Directory.Ldap; namespace LdapAuthConsole { class Program { static string ConvertDomain(string domain) { var r = domain.Split(".") .Select(s => $"dc={s}") .Aggregate((result, element) => $"{result}, {element}"); Console.WriteLine(r); return r; } static void Main(string[] args) { var userName = "foo_user"; // Domain Users var password = "YOUR_PASSWORD"; var domain = "sub.example.co.jp"; var ipAddress = "192.168.xxx.xxx"; var port = 389; var userDn = $"{userName}@{domain}"; try { using (var connection = new LdapConnection { SecureSocketLayer = false }) { connection.Connect(ipAddress, port); connection.Bind(userDn, password); if (connection.Bound){ Console.WriteLine("接続できました"); // 検索 var searchBase = ConvertDomain(domain); // あいまい検索が使える var searchFilter = $"(SAMAccountName=*{userName}*)"; var result = connection.Search( searchBase, // ドメイン直下からすべてのサブを調べる LdapConnection.ScopeSub, searchFilter, new [] { "displayName", // 表示名 "cn", // 表示名と同じ "sn", // 姓 "givenName", // 名 "userPrincipalName", // ユーザーログオン名 "sAMAccountName", // ユーザーログオン名(Windows 2000以前) "description" // 説明 }, false ); var user = result.Next(); var displayName = user.GetAttribute("displayName").StringValue; Console.WriteLine(user); Console.WriteLine(displayName); } else { Console.WriteLine("接続できませんでした"); } } } catch (LdapException ex) { // Log exception // TODO 例外処理を実装 Console.WriteLine("例外が出ました"); Console.WriteLine(ex); } } } }
結果
実行すると、MacでADユーザを使っていなくても、LDAP認証を使ってADから情報を取得できました。
$ dotnet run 接続できました dc=sub, dc=example, dc=co, dc=jp # LDAPで取得した中身 LdapEntry: CN=foo bar,CN=Users,DC=sub,DC=example,DC=co,DC=jp; LdapAttributeSet: LdapAttribute: {type='cn', value='foo bar'} LdapAttribute: {type='sn', value='foo'} LdapAttribute: {type='givenName', value='bar'} LdapAttribute: {type='displayName', value='foo bar'} LdapAttribute: {type='sAMAccountName', value='foo_user'} LdapAttribute: {type='userPrincipalName', value='foo_user@ad.jsl.co.jp'} # GetAttribute()の結果 foo bar
ソースコード
GitHubに上げました。
https://github.com/thinkAmi-sandbox/CSharp_Ldap_Auth-sample
*1:2系は大文字の名前でしたが、3系はCamelCaseになりました