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>
DirectoryEntry。考慮連接池或服務賬號預綁定查詢模式(服務賬號綁定 -> 查詢用戶 DN -> 用戶憑證驗證)。AuthenticationTypes.Secure | AuthenticationTypes.Sealing 支持 SASL 加密。* ( ) \ 等必須轉義,否則查詢錯誤或注入風險。DirectoryServicesCOMException 具體錯誤碼區分「憑證錯誤」與「服務不可用」,前者返回 false,後者拋出/記錄告警。PropertiesToLoad.Add("cn") 僅加載所需屬性,減少網絡開銷。DirectoryEntry/DirectorySearcher 實現 IDisposable,務必 using 包裹。若不允許用戶直接綁定,或需查詢用戶屬性(角色/組),採用雙階段:
此模式更安全、可獲取完整用戶屬性,但需管理服務賬號憑證。