1. 程式人生 > 實用技巧 >如何在你的系統裡整合LDAP統一認證

如何在你的系統裡整合LDAP統一認證

一、為什麼需要統一認證

日常辦公經常會有多套系統,如果各個系統各自維護一套使用者認證,使用者需要記住多個使用者名稱密碼。 系統各自管理使用者認證的方式,不但會有重複建設的問題,使用者體驗也會差,經常會有使用者忘記密碼的情況。

二、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;
}