一、為什么需要統一認證
日常辦公經常會有多套系統,如果各個系統各自維護一套用戶認證,用戶需要記住多個用戶名密碼。 系統各自管理用戶認證的方式,不但會有重復建設的問題,用戶體驗也會差,經常會有用戶忘記密碼的情況。
二、LDAP統一認證是什么
LDAP是Light weight Directory Access Protocol(輕量級目錄訪問協議)的縮寫,它是基於X.500標准的輕量組播目錄訪問協議。
目錄是一個為查詢、瀏覽和搜索而優化的數據庫,它成樹狀結構組織數據。目錄數據庫和關系數據庫不同,它有優異的讀性能,但寫性能很差,沒有事務處理、回滾等復雜操作,不適合存儲修改頻繁的數據。適合存儲人員組織、電話簿和地址簿等信息。
三、LDAP的基本模型
3.1 信息模型
LDAP中信息以樹狀方式組織,數據的基本單元是條目,每個條目由屬性構成,屬性中存儲有屬性值。
3.2 命名模型
LDAP中的命名模型,也即LDAP中條目的定位方式。
每個條目有自己的DN,DN是該條目在整個樹中的唯一名稱標識,如同文件系統中帶路徑的文件名。
3.3 功能模型
LDAP中支持四類操作: 查詢類操作、更新類操作、認證類操作和其它操作;
3.4 安全模型
LDAP的安全模型主要通過身份認證、安全通道和訪問控制來實現。
四、LDAP認證的過程
4.1 訪問LDAP認證服務架構圖
4.2 身份驗證的步驟
LDAP利用登錄名和密碼進行驗證,進行身份驗證通常需要以下步驟:
- 1、通過用戶登錄獲取用戶名密碼。
- 2、匿名或默認用戶綁定LDAP服務器,綁定成功后執行下面步聚。
- 3、根據輸入的登錄名,執行一個搜索。請求參數形如:"(|(uid={login})(mail={login}))“,請求如果返回一個entry,可以通過該entry得到DN,后面步聚使用。如果返回多個或沒有返回,說明用戶輸入用戶名有誤,驗證失敗。
- 4、如果上一步驗證成功,得到用戶信息所在entry的DN,使用這個DN和用戶輸入password重新綁定LDAP服務器。如果綁定成功,說明驗證成功。綁定失敗,返回密碼錯誤的信息。
4.3 為什么需要兩次綁定
為什么基於LDAP進行驗證需要“兩次”綁定? 為什么不能直接取出密碼進行比較?
主要是出於安全考慮,LDAP服務器對於password屬性一般是不可讀的。
4.4 LDAP搜索參數表達式
- & 與(列表中所有項必須為true)
- | 或(列表中至少一個必須為true)
- ! 非(求反的項不能為true)
- = 相等(根據屬性的匹配規則)
- ~= 近似等於(根據屬性的匹配規則)
- >= 大於(根據屬性的匹配規則)
- <= 小於(根據屬性的匹配規則)
- =* 存在(條目中必須有這個屬性,但值不做限制)
- * 通配符(表示這個位置可以有一個或多個字符),當指定屬性值時用到
- \ 轉義符(當遇到“*”,“(”,“)”時進行轉義)
五、如何在系統中集成LDAP認證
LDAP認證服務是跨平台,同時支持TCP/IP協議。在系統中兩次綁定LDAP服務器成功,代表登錄成功,否則登錄失敗。
下面以Java語言為例演示兩次綁定的過程:
首先添加依賴:
<dependency>
<groupId>com.novell.ldap</groupId>
<artifactId>jldap</artifactId>
<version>4.3</version>
</dependency>
兩次綁定代碼:
public string bind(String username, String password) {
LDAPConnection ldapConnection = new LDAPConnection();
ldapConnection.connect(Constants.LDAP_HOST, Constants.LDAP_PORT);
ldapConnection.bind(LDAPConnection.LDAP_V3, Constants.LDAP_BIND_DN, Constants.LDAP_BIND_PASSWORD.getBytes("UTF8"));
try {
String filter = String.format("(|(mail=%s)(uid=%s))", username, username);
LDAPSearchResults results = ldapConnection.search(Constants.LDAP_BIND_BASE, LDAPConnection.SCOPE_SUB, filter, null, false);
LDAPEntry nextEntry, nextUserEntry;
while (results.hasMore()) {
try {
nextEntry = results.next();
} catch (LDAPException e) {
if (e.getResultCode() == LDAPException.LDAP_TIMEOUT || e.getResultCode() == LDAPException.CONNECT_ERROR) {
break;
} else {
continue;
}
}
String dn = nextEntry.getDN();
ldapConnection.bind(LDAPConnection.LDAP_V3, dn, password.getBytes("UTF8"));
LDAPSearchResults userResults = ldapConnection.search(Constants.LDAP_BIND_BASE, LDAPConnection.SCOPE_SUB, String.format("(|(mail=%s)(uid=%s))", username, username), null, false);
while (userResults.hasMore()) {
try {
nextUserEntry = userResults.next();
} catch (LDAPException e) {
if (e.getResultCode() == LDAPException.LDAP_TIMEOUT || e.getResultCode() == LDAPException.CONNECT_ERROR) {
break;
} else {
continue;
}
}
if (nextUserEntry == null) {
continue;
}
String userDn = nextUserEntry.getDN();
if (!Strings.isNullOrEmpty(userDn) && userDn.equals(dn)) {
//登錄成功
}
}
}
} catch (LDAPException e) {
logger.warn("get LDAPException:", e);
} catch (UnsupportedEncodingException e) {
logger.warn("get UnsupportedEncodingException:", e);
} finally {
ldapConnection.disconnect();
}
return null;
}