1. 程式人生 > 其它 >LDAP學習筆記

LDAP學習筆記

技術標籤:javaldapjavajndi

我們可以通過JNDI的API來操作LDAP

LDAP v3

國際化

通過國際字符集(ISO 10646)處理國際化,以表示字串形式的協議元素(例如DN)

LDAP v3與 LDAP v2版本的區別之一:使用UTF-8對字串編碼

認證方式

LDAP的不同版本支援不同型別的身份驗證。

LDAP v2支援匿名,簡單(明文密碼)和Kerberos v4身份驗證。

LDAP v3支援匿名,簡單和SASL身份驗證。

SASL是簡單身份驗證和安全層(RFC 2222)。它指定了質詢-響應協議,在該協議中,為了進行身份驗證和建立安全層以在其上進行後續通訊,在客戶端和伺服器之間交換資料。通過使用SASL,LDAP可以支援LDAP客戶端和伺服器同意的任何型別的身份驗證。

當前定義了幾種SASL機制:DIGEST-MD5,CRAM-MD5,Anonymous,External,S/Key,GSSAPI, 和Kerberos v4。 LDAP v3客戶端可以使用任何這些SASL機制,只要LDAP v3伺服器支援它們即可。此外,可以使用新的(尚待定義)SASL機制,而不必對LDAP進行更改。

傳送 LDAP 請求而不執行"繫結"的客戶端將被視為匿名‎‎客戶端

通過使用Context.SECURITY_AUTHENTICATION環境屬性指定身份驗證機制。該屬性可以具有以下值之一。

屬性描述
sasl_mech用空格分隔的SASL機制名稱列表。使用列出的SASL機制之一(例如,“ CRAM-MD5”表示使用RFC 2195中描述的CRAM-MD5 SASL機制)。
none不使用身份驗證(匿名)
simple使用弱認證(明文密碼)

如果客戶端未指定任何身份驗證環境屬性,則預設身份驗證機制為“none”。然後,該客戶端將被視為匿名客戶端。

如果客戶端指定身份驗證資訊而未顯式指定Context.SECURITY_AUTHENTICATION屬性,則預設身份驗證機制為“simple”。

匿名認證(none)

如前所述,如果未設定身份驗證環境屬性,則預設身份驗證機制為“none”。如果客戶端將Context.SECURITY_AUTHENTICATION環境屬性設定為“ none”,則身份驗證機制為“ none”,並且所有其他身份驗證環境屬性都將被忽略。您只需要明確地執行此操作,以確保可以忽略任何可能已設定的其他身份驗證屬性。無論哪種情況,客戶端都將被視為匿名客戶端。這意味著伺服器不知道或不在乎客戶端是誰,並且將允許客戶端訪問(讀取和更新)已配置為可由任何未經身份驗證的客戶端訪問的任何資料。

這是一個將Context.SECURITY_AUTHENTICATION屬性顯式設定為“ none”的示例(即使這樣做不是嚴格必要的,因為這是預設設定)。

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Use anonymous authentication
env.put(Context.SECURITY_AUTHENTICATION, "none");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

明文密碼認證(simple)

simple身份驗證包括向LDAP伺服器傳送客戶端(使用者)的標準DN和客戶端的明文密碼(請參閱RFC 2251和RFC 2829)。由於可以從網路讀取密碼,因此該機制存在安全問題。為了避免以這種方式公開密碼,您可以在加密通道(例如SSL)中使用simple的身份驗證機制,前提是LDAP伺服器支援該機制。

要使用simple的身份驗證機制,必須按如下所示設定三個身份驗證環境屬性。

  • Context.SECURITY_AUTHENTICATION 屬性值設定為"simple"

  • Context.SECURITY_PRINCIPAL 設定為要認證的實體的標準DN(例如,“ cn = S.User,ou = NewHires,o = JNDITutorial”)。它的型別為java.lang.String。

  • Context.SECURITY_CREDENTIALS 設定為主體的密碼(例如“ mysecret”)。它的型別為java.lang.String,char陣列(char [])或位元組陣列(byte [])。如果密碼是java.lang.String或char陣列,則對LDAP v3使用UTF-8進行編碼,對於LDAP v2使用ISO-Latin-1進行編碼,以將其傳輸到伺服器。如果密碼是byte [],則將密碼原樣傳送到伺服器。

Note:

如果您為Context.SECURITY_CREDENTIALS環境屬性提供一個空字串,一個空位元組/字元陣列或null,那麼身份驗證機制將為“none”。這是因為LDAP要求密碼對於簡單身份驗證為非空。如果未提供密碼,則協議會自動將身份驗證轉換為“none”。

SASL認證

LDAP v3協議使用SASL支援可插入身份驗證。這意味著LDAP客戶端和伺服器可以配置為協商和使用可能的非標準和/或自定義機制進行身份驗證,具體取決於客戶端和伺服器所需的保護級別。 LDAP v2協議不支援SASL。

當前定義了幾種SASL機制:

在上一個列表中的機制中,流行的LDAP伺服器(例如來自Oracle,OpenLDAP和Microsoft的那些)支援External, Digest-MD5, 和Kerberos V5。 RFC 2829建議使用Digest-MD5作為LDAP v3伺服器的強制性預設機制。

這是一個簡單的程式,用於查詢LDAP伺服器支援的SASL機制的列表:

// Create initial context
DirContext ctx = new InitialDirContext();

// Read supportedSASLMechanisms from root DSE
Attributes attrs = ctx.getAttributes(
    "ldap://localhost:389", new String[]{"supportedSASLMechanisms"});

這是通過在支援External SASL機制的伺服器上執行該程式所產生的輸出。

{supportedsaslmechanisms=supportedSASLMechanisms: 
                         EXTERNAL, GSSAPI, DIGEST-MD5}

若要使用特定的SASL機制,請在Context.SECURITY_AUTHENTICATION環境屬性中指定其Internet分配號碼授權機構(IANA)註冊的機制名稱。您還可以指定LDAP提供程式嘗試的機制列表。這是通過指定以空格分隔的機制名稱的有序列表來完成的。 LDAP提供程式將使用它找到實現的第一種機制。

這是一個示例,要求LDAP提供程式嘗試獲取DIGEST-MD5機制的實現,如果該實現不可用,則將其用於GSSAPI。

env.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5 GSSAPI");

您可能會從應用程式使用者那裡獲得身份驗證機制的列表。或者,您可以通過類似於前面顯示的呼叫來詢問LDAP伺服器來獲取它。 LDAP提供程式本身不諮詢伺服器以獲取此資訊。它只是嘗試查詢和使用指定機制的實現。

平臺中的LDAP提供程式具有對External, Digest-MD5, 和GSSAPI (Kerberos v5)SASL機制的內建支援。您可以新增對其他機制的支援。

後續的DIGEST-MD5和自定義認證方式就不翻譯了~~

使用JNDI向LDAP進行身份驗證

在JNDI中,認證資訊在環境屬性中指定。使用InitialDirContext類(或其超類或子類)建立初始上下文時,將提供一組環境屬性,其中一些可能包含身份驗證資訊。你可以使用以下環境屬性來指定認證資訊。

  • Context.SECURITY_AUTHENTICATION("java.naming.security.authentication")指定要使用的身份驗證機制。對於JDK中的LDAP服務提供者,它可以是以下字串之一:“ none”,“ simple”,sasl_mech,其中sasl_mech是用空格分隔的SASL機制名稱列表。
  • Context.SECURITY_PRINCIPAL("java.naming.security.principal")指定執行身份驗證的使用者/程式的名稱,並取決於Context.SECURITY_AUTHENTICATION屬性的值。
  • Context.SECURITY_CREDENTIALS("java.naming.security.credentials")指定執行身份驗證的使用者/程式的憑據,並取決於Context.SECURITY_AUTHENTICATION屬性的值。

建立初始上下文後,底層LDAP服務提供者從這些環境屬性中提取身份驗證資訊,並使用LDAP“繫結”操作將它們傳遞給伺服器。

// Set up the environment for creating the initial context
Hashtable<String, Object> env = new Hashtable<String, Object>();
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Authenticate as S. User and password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

如果要對現有上下文使用不同的身份驗證資訊,則可以使用Context.addToEnvironment()和Context.removeFromEnvironment()來更新包含身份驗證資訊的環境屬性。上下文中方法的後續呼叫將使用新的身份驗證資訊與伺服器進行通訊。

下面的示例顯示建立上下文後,上下文的身份驗證資訊如何更改為“none”。

// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

// Create the initial context
DirContext ctx = new InitialDirContext(env);

// ... do something useful with ctx

// Change to using no authentication
ctx.addToEnvironment(Context.SECURITY_AUTHENTICATION, "none");

// ... do something useful with ctx

設定連線超時

// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, 
    "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Specify timeout to be 5 seconds
env.put("com.sun.jndi.ldap.connect.timeout", "5000");

// Create initial context
DirContext ctx = new InitialDirContext(env);

// do something useful with ctx

在此示例中,如果無法在5秒鐘內建立連線,則將引發異常。

如果Context.PROVIDER_URL屬性包含多個URL,則提供程式將對每個URL使用超時。例如,如果有3個URL,並且將超時指定為5秒,則提供程式將總共等待最多15秒。

關閉

普通垃圾回收會在不再使用Context例項時將其刪除。由Context例項進行垃圾回收的連線將自動關閉。因此,您不需要顯式關閉連線。但是,網路連線是有限的資源,對於某些程式,您可能希望控制它們的擴散和使用。

顯示關閉

你在Context例項上呼叫Context.close()來指示您不再需要使用它。如果要關閉的Context例項正在使用專用連線,則該連線也將關閉。如果Context例項與其他Context和未終止的NamingEnumeration例項共享連線,則在所有此類Context和NamingEnumeration例項上呼叫close()之前,不會關閉該連線。

// Create initial context
DirContext ctx = new InitialDirContext(env);

// Get a copy of the same context
Context ctx2 = (Context)ctx.lookup("");

// Get a child context
Context ctx3 = (Context) ctx.lookup("ou=NewHires");

// do something useful with ctx, ctx2, ctx3

// Close the contexts when we're done
ctx.close();
ctx2.close();
ctx3.close();

強制隱式關閉

如前所述,對於那些不在範圍內的Context和NamingEnumeration例項,Java執行時系統最終將對其進行垃圾回收,從而清除close()會完成的狀態。要強制垃圾回收,可以使用以下程式碼。

Runtime.getRuntime().gc();
Runtime.getRuntime().runFinalization();

根據程式的狀態,執行此過程可能會導致嚴重的(臨時的)效能下降。如果需要確保關閉連線,請跟蹤Context例項並顯式關閉它們。

檢測連線關閉

LDAP伺服器通常有一個空閒超時時間,在此之後它們將關閉不再使用的連線。當你隨後在使用此類連線的Context例項上呼叫方法時,該方法將引發CommunicationException。若要檢測伺服器何時關閉Context例項正在使用的連線,請向Context例項註冊一個UnsolicitedNotificationListener。 LDAP未經請求的通知部分中顯示了一個示例。儘管該示例旨在用於接收來自伺服器的未經請求的通知,但它也可以用於檢測伺服器的連線關閉。啟動程式後,停止LDAP伺服器,並觀察呼叫了偵聽器的namingExceptionThrown()方法。

連線池

LDAP服務提供者支援的另一種型別的連線共享稱為連線池。在這種共享型別中,LDAP服務提供者維護一個(可能)以前使用過的連線池,並根據需要將它們分配給上下文例項。當上下文例項通過連線完成時(關閉或垃圾回收),連線被返回到池中供將來使用。請注意,這種共享形式是順序的:連線從池中檢索,使用,返回到池中,然後從池中檢索另一個上下文例項。

連線池是每個Java執行時系統維護的。對於某些情況,連線池可以顯著提高效能。例如,如果使用連線池,則處理包含對同一LDAP伺服器的四個引用的搜尋響應只需要一個連線。如果沒有連線池,這樣的場景將需要四個獨立的連線。

您可以通過將屬性“ com.sun.jndi.ldap.connect.pool”新增到傳遞給初始上下文建構函式的環境屬性來請求連線池。這是一個例子。

// Set up environment for creating initial context
Hashtable env = new Hashtable(11);
env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
env.put(Context.PROVIDER_URL, "ldap://localhost:389/o=JNDITutorial");

// Enable connection pooling
env.put("com.sun.jndi.ldap.connect.pool", "true");

// Create one initial context (Get connection from pool)
DirContext ctx = new InitialDirContext(env);

// do something useful with ctx

// Close the context when we're done
ctx.close();   // Return connection to pool

// Create another initial context (Get connection from pool)
DirContext ctx2 = new InitialDirContext(env);

// do something useful with ctx2

// Close the context when we're done
ctx2.close();   // Return connection to pool

本示例連續建立兩個初始上下文。第二個初始上下文將重用第一個初始上下文。要執行此程式並觀察如何檢索連線並將其返回到池,請使用以下命令列。

#java -Dcom.sun.jndi.ldap.connect.pool.debug=fine UsePool

這將產生如下所示的輸出。

Create [email protected][localhost:389]
Use [email protected]
{ou=ou: NewHires, objectclass=objectClass: top, organizationalUnit}
Release [email protected]
Use [email protected]
{ou=ou: People, objectclass=objectClass: top, organizationalunit}
Release [email protected]

你可以通過包含或省略“ com.sun.jndi.ldap.connect.pool”屬性來決定何時以及在何處使用池,從而基於上下文控制池。在上一個示例中,如果在建立第二個初始上下文之前從環境屬性中刪除了此屬性,則第二個初始上下文將不使用池化連線。

LDAP提供程式通過應用程式的指示跟蹤連線是否正在被使用。它假定維護開放上下文控制代碼的應用程式正在使用連線。因此,為了讓LDAP提供者正確地管理池連線,你必須在你不再需要的上下文上謹慎地呼叫Context.close()。

LDAP提供程式會自動檢測到錯誤的連線並將其從池中刪除。無論是否使用連線池,上下文最終以錯誤的連線結尾的可能性都是相同的。

LDAP服務提供商維護的連線池可能會受到限制;連線池配置部分中對此進行了詳細描述。當啟用了連線池並且沒有池連線可用時,客戶端應用程式將阻塞,等待可用的連線。您可以使用“ com.sun.jndi.ldap.connect.timeout”環境屬性來指定等待池化連線的時間。如果省略此屬性,則應用程式將無限期等待。

什麼時候不使用池!!

池連線旨在重用。因此,如果計劃在Context例項上執行可能會更改基礎連線狀態的操作,則不應為該Context例項使用連線池。例如,如果您計劃在Context例項上呼叫“啟動TLS”擴充套件操作,或者計劃在之後更改與安全相關的屬性(例如“ java.naming.security.principal”或“ java.naming.security.protocol”),如果初始上下文已建立,則不應為該Context例項使用連線池,因為LDAP提供程式不會跟蹤任何此類狀態更改。如果在這種情況下使用連線池,則可能會損害應用程式的安全性。

連線池的配置這裡也不過多概述

身份驗證失敗

身份驗證可能由於多種原因而失敗。例如,如果您提供了錯誤的身份驗證資訊,例如錯誤的密碼或主體名稱,則將丟擲AuthenticationException。
這是先前示例的變體示例。這次,密碼錯誤會導致身份驗證失敗。

// Authenticate as S. User and give an incorrect password
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "notmysecret");

這將產生以下輸出:

javax.naming.AuthenticationException: [LDAP: error code 49 - Invalid Credentials]

由於不同的伺服器支援不同的身份驗證機制,因此您可能會請求伺服器不支援的身份驗證機制。在這種情況下,將丟擲AuthenticationNotSupportedException。

這是先前示例的變體示例。這次,不支援的身份驗證機制('custom')導致身份驗證失敗。

// Authenticate as S. User and the password "mysecret"
env.put(Context.SECURITY_AUTHENTICATION, "custom");
env.put(Context.SECURITY_PRINCIPAL, 
        "cn=S. User, ou=NewHires, o=JNDITutorial");
env.put(Context.SECURITY_CREDENTIALS, "mysecret");

這將產生以下輸出:

javax.naming.AuthenticationNotSupportedException: custom

LDAPCompare

LDAP“Compare”操作允許客戶端詢問伺服器命名條目是否具有屬性/值對。這允許伺服器保持某些屬性/值對的機密性(例如,不為一般的“搜尋”訪問而暴露),同時仍然允許客戶端有限地使用它們。

例如,有些伺服器可能將此特性用於密碼,儘管客戶機在“compare”操作本身傳遞明文密碼是不安全的。

要在JNDI中完成此工作,請為以下方法使用適當約束的引數:

1.過濾器的形式必須是"(name=value)"。你不能用萬用字元。

2.搜尋範圍必須是SearchControls.OBJECT_SCOPE。

3.您必須請求不返回任何屬性。如果不滿足這些條件,那麼這些方法將使用LDAP“search”操作而不是LDAP“compare”操作。

下面是一個使用LDAP“比較”操作的示例:

// Value of the attribute
byte[] key = {(byte)0x61, (byte)0x62, (byte)0x63, (byte)0x64, 
              (byte)0x65, (byte)0x66, (byte)0x67};

// Set up the search controls
SearchControls ctls = new SearchControls();
ctls.setReturningAttributes(new String[0]);       // Return no attrs
ctls.setSearchScope(SearchControls.OBJECT_SCOPE); // Search object only

// Invoke search method that will use the LDAP "compare" operation
NamingEnumeration answer = ctx.search("cn=S. User, ou=NewHires", 
                                      "(mySpecialKey={0})", 
                                       new Object[]{key}, ctls);

如果比較成功,則結果列舉將包含一個名稱為空名稱且不包含任何屬性的專案。

Search Result

在DirContext介面中使用搜索方法時,將返回NamingEnumeration。 NamingEnumeration中的每個專案都是一個SearchResult,其中包含以下資訊:

每個SearchResult包含滿足搜尋過濾條件的LDAP條目的名稱。您可以通過使用getName()獲得條目的名稱。此方法返回相對於目標上下文的LDAP條目的組合名稱。目標上下文是name引數解析到的上下文。用LDAP的話來說,目標上下文是搜尋的基礎物件。這是一個例子。

NamingEnumeration answer = ctx.search("ou=NewHires", 
    "(&(mySpecialKey={0}) (cn=*{1}))",  // Filter expression
    new Object[]{key, name},                // Filter arguments
    null);                                  // Default search controls

在此示例中,目標上下文是由“ ou = NewHires”命名的上下文。SearchResults在answer中的名稱是相對於“ ou = NewHires”的。例如,如果getName()返回“ cn = J.Duke”,則其相對於ctx的名稱將為“ cn = J.Duke,ou = NewHires”。

如果使用SearchControls.SUBTREE_SCOPE或SearchControls.OBJECT_SCOPE執行搜尋,並且目標上下文字身滿足搜尋過濾器的要求,則返回的名稱將為“”(空名稱),因為這是相對於目標上下文的名稱。

這一塊我看的不是很懂,暫時先放著

LDAP主動通知

LDAP v3 (RFC 2251)定義了一個未經請求的通知,這是一個由LDAP伺服器傳送到客戶機的訊息,客戶機沒有任何挑釁。未經請求的通知在JNDI中由UnsolicitedNotification介面表示。

由於未經請求的通知是由伺服器非同步傳送的,因此可以使用與接收有關名稱空間更改和物件內容更改的通知相同的事件模型。你如果有興趣接收未經請求的通知可以通過向EventContext或EventDirContext註冊UnsolicitedNotificationListener

下面是UnsolicitedNotificationListener的一個示例。

public class UnsolListener implements UnsolicitedNotificationListener {
    public void notificationReceived(UnsolicitedNotificationEvent evt) {
        System.out.println("received: " + evt);
    }

    public void namingExceptionThrown(NamingExceptionEvent evt) {
        System.out.println(">>> UnsolListener got an exception");
            evt.getException().printStackTrace();
    }
}

下面是一個向事件源註冊UnsolicitedNotificationListener實現的示例。請注意,只有EventContext.addNamingListener()的listener引數是相關的。名稱和範圍引數與未經請求的通知無關。

// Get the event context for registering the listener
EventContext ctx = (EventContext)
    (new InitialContext(env).lookup("ou=People"));

// Create the listener
NamingListener listener = new UnsolListener();

// Register the listener with the context (all targets equivalent)
ctx.addNamingListener("", EventContext.ONELEVEL_SCOPE, listener);

在執行這個程式時,您需要將它指向一個LDAP伺服器,該伺服器可以生成未經請求的通知,並促使伺服器發出通知。否則,一分鐘後程序將無聲地退出。

實現UnsolicitedNotificationListener的偵聽器也可以實現其他NamingListener介面,例如NamespaceChangeListener和ObjectChangeListener。

最後附上一個例子:



import javax.naming.Context;
import javax.naming.NamingEnumeration;
import javax.naming.NamingException;
import javax.naming.directory.Attribute;
import javax.naming.directory.Attributes;
import javax.naming.directory.SearchControls;
import javax.naming.directory.SearchResult;
import javax.naming.ldap.InitialLdapContext;
import javax.naming.ldap.LdapContext;
import java.util.Hashtable;

/**
 * Ldap util
 */
public class LdapConnectUtil {

    private static final String LDAP_URL = "ldap://192.168.201.201:389";
    private static final String USER_NAME = "Administrator";
    private static final String DOMAIN = "xxx.xxx.com";
    private static final String DOMAIN_NODE = "dc=aaa,dc=bbb,dc=ccc";
    private static final String PASSWORD = "123456";

    /**
     * connect Ldap
     * @return LdapContext
     */
    public static LdapContext adLogin() {
        try {
            Hashtable<String, String> env = new Hashtable<>();
            env.put(Context.SECURITY_PRINCIPAL, USER_NAME+"@"+DOMAIN);
            env.put(Context.SECURITY_CREDENTIALS, PASSWORD);
            env.put(Context.PROVIDER_URL, LDAP_URL);
            env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory");
            // 認證機制 sasl_mech
            env.put(Context.SECURITY_AUTHENTICATION, "simple");
            return new InitialLdapContext(env, null);

        } catch (NamingException e) {
            e.printStackTrace();
            return null;
        }
    }

    public static String checkUserExist(String name) {
        LdapContext ldapContext = adLogin();
        if (null != ldapContext){
            try {
                // Query domain account
                String searchFilter = "(sAMAccountName="+name+")";
                SearchControls searchControls = new SearchControls();
                //String[]  returnedAtts = {"description","sAMAccountName","userAccountControl"};
                String[]  returnedAtts = {"description","sAMAccountName","distinguishedName"};
                // Set the specified return field, if not set, return all
                searchControls.setReturningAttributes(returnedAtts);
                // Set search range , depth
                searchControls.setSearchScope(SearchControls.SUBTREE_SCOPE);
                // Search LDAP according to the set domain node, filter class and search controller to get the result
                NamingEnumeration<SearchResult> searchResults = ldapContext.search(DOMAIN_NODE, searchFilter,searchControls);
                Attributes attributes = searchResults.next().getAttributes();
                Attribute distinguishedName = attributes.get("distinguishedName");
                if (null != distinguishedName){
                    return distinguishedName.get().toString();
                }else {
                    return null;
                }

            } catch (NamingException e) {
                e.printStackTrace();
            } finally {
                try{
                    ldapContext.close();
                }catch (Exception e){
                    e.printStackTrace();
                }
            }
        }
        return null;
    }
}