首頁
編程語言
C語言|JAVA編程
Python編程
網頁編程
ASP編程|PHP編程
JSP編程
數據庫
MYSQL數據庫|SqlServer數據庫
Oracle數據庫|DB2數據庫
您现在的位置: 程式碼之家 >> 編程語言 >  >> .NET 網頁程式設計 >> C# >> 關於C#

C# 基於 Domino LDAP 實現用戶身份驗證:完整實戰代碼

     編輯:關於C#

一、Domino LDAP 環境前置條件

  • LDAP 服務啟用且**禁止匿名訪問**(否則無驗證意義)。
  • Internet 認證設置:較多名稱變換、較低安全性。支持用戶名格式:全名、簡稱、Email 地址。
  • 密碼字段:Internet 密碼(非 Notes 密碼)。
  • 默認 Schema 無 uid(簡稱)屬性,需手動添加或改用 mail/cn 查詢。

二、核心驗證原理

使用用戶輸入的用戶名/密碼建立 DirectoryEntry 綁定,執行任意查詢(如 FindOne())。綁定成功即驗證通過,失敗拋出 DirectoryServicesCOMException(錯誤碼 0x8007203A / 8007052E)。

三、生產級封裝類

using System;
using System.DirectoryServices;
using System.Configuration;

public class DominoLdapAuthenticator {
    private readonly string _ldapServer;
    private readonly string _domain; // 可選:用於構建完整 DN
    
    public DominoLdapAuthenticator(string ldapServer, string domain = null) {
        _ldapServer = ldapServer ?? throw new ArgumentNullException(nameof(ldapServer));
        _domain = domain;
    }
    
    /// 
    /// 驗證用戶憑證
    /// 
    /// 用戶名(全名/簡稱/Email)
    /// Internet 密碼
    /// 驗證通過返回 true
    public bool ValidateUser(string username, string password) {
        if (string.IsNullOrWhiteSpace(username) || password == null) return false;
        
        try {
            using (var entry = CreateDirectoryEntry(username, password)) {
                using (var searcher = new DirectorySearcher(entry)) {
                    // 針對 Domino Person 對象,支持 cn/uid/mail 多字段匹配
                    searcher.Filter = $"(&(objectClass=dominoPerson)(|(cn={EscapeLdapFilter(username)})(uid={EscapeLdapFilter(username)})(mail={EscapeLdapFilter(username)})))";
                    searcher.SearchScope = SearchScope.Subtree;
                    searcher.PropertiesToLoad.Add("cn"); // 僅加載必要屬性
                    
                    // 關鍵:觸發綁定驗證
                    SearchResult result = searcher.FindOne();
                    return result != null;
                }
            }
        } catch (DirectoryServicesCOMException ex) when (ex.ErrorCode == -2147019886 || ex.ErrorCode == -2147023570) {
            // 0x8007203A: 無效憑證 / 0x8007052E: 登錄失敗
            return false;
        } catch (Exception) {
            // 網絡錯誤、服務不可用等記錄日誌後返回 false
            return false;
        }
    }
    
    private DirectoryEntry CreateDirectoryEntry(string username, string password) {
        // Domino 通常不需要完整 DN,直接用用戶名綁定
        string path = $"LDAP://{_ldapServer}";
        return new DirectoryEntry(path, username, password, AuthenticationTypes.Secure | AuthenticationTypes.Sealing);
    }
    
    /// 
    /// 轉義 LDAP 查詢特殊字符,防注入
    /// 
    private static string EscapeLdapFilter(string input) {
        // RFC 4515 特殊字符: * ( ) \ NUL
        return input
            .Replace("\\", "\\5c")
            .Replace("*", "\\2a")
            .Replace("(", "\\28")
            .Replace(")", "\\29")
            .Replace("\0", "\\00");
    }
}

四、調用示例

public class LoginService {
    private readonly DominoLdapAuthenticator _auth;
    
    public LoginService() {
        // 從配置文件讀取
        string ldapServer = ConfigurationManager.AppSettings["DominoLdapServer"]; // 如 "192.168.1.100:389"
        _auth = new DominoLdapAuthenticator(ldapServer);
    }
    
    public bool Login(string username, string password) {
        bool valid = _auth.ValidateUser(username, password);
        if (valid) {
            // 可選:獲取用戶詳細信息(角色、部門等)
            var userInfo = GetUserDetails(username);
            // 建立會話、生成 Token 等
        }
        return valid;
    }
    
    private UserDetail GetUserDetails(string username) {
        // 使用服務賬號綁定查詢詳情...
        return new UserDetail();
    }
}

五、配置文件範例

<configuration>
  <appSettings>
    <add key="DominoLdapServer" value="192.168.1.100:389" />
    <!-- 可選:SSL 端口 636 -->
    <add key="DominoLdapServerSsl" value="192.168.1.100:636" />
  </appSettings>
</configuration>

六、關鍵最佳實踐與避坑

  1. 連接復用:高併發場景下,勿頻繁創建 DirectoryEntry。考慮連接池或服務賬號預綁定查詢模式(服務賬號綁定 -> 查詢用戶 DN -> 用戶憑證驗證)。
  2. SSL/TLS:生產環境**強制使用 LDAPS (636)** 或 StartTLS,防止密碼明文傳輸。AuthenticationTypes.Secure | AuthenticationTypes.Sealing 支持 SASL 加密。
  3. 特殊字符轉義:用戶名含 * ( ) \ 等必須轉義,否則查詢錯誤或注入風險。
  4. 錯誤碼處理:捕獲 DirectoryServicesCOMException 具體錯誤碼區分「憑證錯誤」與「服務不可用」,前者返回 false,後者拋出/記錄告警。
  5. 屬性加載優化:PropertiesToLoad.Add("cn") 僅加載所需屬性,減少網絡開銷。
  6. 資源釋放:DirectoryEntry/DirectorySearcher 實現 IDisposable,務必 using 包裹。

七、替代方案:服務賬號模式

若不允許用戶直接綁定,或需查詢用戶屬性(角色/組),採用雙階段:

  1. 服務賬號綁定 LDAP,查詢用戶 DN(按 cn/uid/mail)。
  2. 用查得的 DN + 用戶密碼嘗試綁定驗證。

此模式更安全、可獲取完整用戶屬性,但需管理服務賬號憑證。

  1. 上一篇文章:
  2. 下一篇文章:
Copyright © 程式碼之家 All Rights Reserved