如何在你的系統裡整合LDAP統一認證
阿新 • • 發佈:2020-09-19
一、為什麼需要統一認證
日常辦公經常會有多套系統,如果各個系統各自維護一套使用者認證,使用者需要記住多個使用者名稱密碼。 系統各自管理使用者認證的方式,不但會有重複建設的問題,使用者體驗也會差,經常會有使用者忘記密碼的情況。
二、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;
}