LDAP dan JNDI: Bersama selamanya

Lightweight Directory Access Protocol (LDAP), yang menelusuri akarnya ke protokol X.500, dikembangkan pada awal 1990-an sebagai protokol direktori standar. LDAP menentukan bagaimana klien harus mengakses data di server, bukan bagaimana data itu disimpan di server. Ini memungkinkan LDAP menjadi antarmuka untuk semua jenis penyimpanan data.

( Catatan: Untuk mengunduh kode sumber lengkap artikel ini, lihat bagian Sumberdaya di bawah.)

Struktur dasar LDAP didasarkan pada metafora pohon informasi sederhana yang disebut pohon informasi direktori (DIT). Setiap daun di pohon adalah pintu masuk; entri tingkat pertama atau teratas adalah entri root. Entri menyertakan nama yang dibedakan (DN) dan sejumlah pasangan atribut / nilai. DN, yang merupakan nama entri, harus unik. Ini menunjukkan hubungan antara entri dan sisa DIT dengan cara yang mirip dengan cara di mana nama jalur lengkap file menunjukkan hubungannya dengan sisa file dalam sistem file. Sementara jalur ke file membaca dari kiri ke kanan, DN, sebaliknya, membaca dari kanan ke kiri. Berikut adalah contoh DN:

uid = styagi, ou = orang, o = myserver.com 

Bagian paling kiri dari DN, disebut nama yang dibedakan relatif (RDN), terdiri dari pasangan atribut / nilai. Dalam contoh di atas, pasangan ini adalah uid=styagi. Atribut LDAP sering menggunakan mnemonik, beberapa contohnya tercantum dalam Tabel 1.

o Organisasi
ou Unit organisasi
cn Nama yang umum
sn Nama keluarga
givenname Nama depan
uid Identitas pengguna
dn Nama yang dibedakan
mail Alamat email
Tabel 1. Beberapa atribut LDAP umum

Informasi tentang atribut, aturan pencocokan atribut, dan hubungan antara kelas objek ditentukan dalam skema server . Setiap atribut dapat memiliki satu atau beberapa nilai, bergantung pada bagaimana skema itu didefinisikan. Seorang pengguna, misalnya, dapat memiliki lebih dari satu alamat email. Ada juga atribut khusus yang disebut objectclass yang menentukan atribut yang diperlukan dan diperbolehkan untuk entri tertentu. Seperti objek di Java, objectclass di LDAP dapat diperpanjang untuk mempertahankan atribut yang ada dan menambahkan atribut baru.

Layanan penamaan mengaitkan nama dengan objek dan menemukan objek berdasarkan nama yang diberikan. (Registri RMI adalah contoh bagus dari layanan penamaan.) Banyak layanan penamaan diperluas dengan layanan direktori. Meskipun layanan penamaan memungkinkan pencarian objek berdasarkan namanya, layanan direktori juga memungkinkan objek tersebut memiliki atribut. Hasilnya, dengan layanan direktori kita dapat mencari atribut objek atau mencari objek berdasarkan atributnya.

Jadi, di manakah JNDI cocok dengan jargon LDAP ini? JNDI melakukan untuk LDAP seperti yang dilakukan JDBC untuk Oracle - menyediakan API standar untuk berinteraksi dengan layanan penamaan dan direktori menggunakan antarmuka penyedia layanan (SPI), yang serupa dengan driver JDBC. LDAP adalah cara standar untuk memberikan akses ke informasi direktori. JNDI memberikan aplikasi dan objek Java antarmuka yang kuat dan transparan untuk mengakses layanan direktori seperti LDAP. Tabel 2 di bawah menguraikan operasi LDAP umum dan setara JNDI mereka. (Untuk melihat detail spesifikasi JNDI, lihat Sumberdaya.)

Operasi Apa yang dilakukannya Setara dengan JNDI
Cari Cari direktori untuk entri direktori yang cocok DirContext.search()
Membandingkan Bandingkan entri direktori dengan sekumpulan atribut DirContext.search()
Menambahkan Tambahkan entri direktori baru DirContext.bind(), DirContext.createSubcontext()
Memodifikasi Ubah entri direktori tertentu DirContext.modifyAttributes()
Menghapus Hapus entri direktori tertentu Context.unbind(), Context.destroySubcontext()
Ganti nama Ubah nama atau modifikasi DN Context.rename()
Mengikat Mulailah sesi dengan server LDAP new InitialDirContext()
Memperlonggar Akhiri sesi dengan server LDAP Context.close()
Mengabaikan Abaikan operasi yang sebelumnya dikirim ke server Context.close(), NamingEnumneration.close()
Extended Extended operations command LdapContext.extendedOperation()
Tabel 2. Operasi LDAP umum dan setara JNDI

Memanipulasi objek di server LDAP

Mari langsung ke pengejaran dan lihat bagaimana memanipulasi objek di server LDAP. Operasi LDAP standar meliputi:

  • Hubungkan ke server
  • Ikat ke server (anggap ini sebagai otentikasi)
  • Tambahkan entri baru di server LDAP
  • Ubah entri
  • Hapus entri
  • Cari entri di server

Kami akan memeriksa setiap langkah ini pada bagian di bawah ini, dengan contohnya.

Sebelum menjalankan contoh, Anda perlu menginstal server LDAP, kelas JNDI, dan (kecuali jika Anda ingin menonaktifkan pemeriksaan skema) skema Java. Anda dapat menemukan informasi penginstalan di direktori skema file zip JNDI. Contoh kami menggunakan Netscape Directory Server 4.1 dan JDK 2. (Untuk menginstal paket ini, lihat Sumber.)

Hubungkan ke server

Untuk menyambung ke server, Anda harus mendapatkan referensi ke objek yang mengimplementasikan DirContextantarmuka. Di sebagian besar aplikasi, ini dilakukan dengan menggunakan InitialDirContextobjek yang mengambil Hashtableargumen. The Hashtableberisi berbagai entri, seperti nama host, port, dan JNDI kelas penyedia layanan untuk menggunakan:

Hashtable env = baru Hashtable (); env.put (Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); env.put (Context.PROVIDER_URL, "ldap: // localhost: 389"); DirContext ctx = new InitialDirContext (env);

Ikat ke Server

Setelah terhubung, klien mungkin perlu mengotentikasi dirinya sendiri; proses ini juga dikenal sebagai pengikatan ke server. (Ketahuilah bahwa kata mengikat juga dapat merujuk pada tindakan menambahkan sesuatu ke direktori.)

Dalam LDAP versi 2, semua klien harus mengautentikasi saat menghubungkan, tetapi versi 3 secara default menggunakan anonim dan, jika nilai default digunakan, koneksi juga anonim. Server LDAP mempertahankan hak menggunakan daftar kontrol akses (ACL) yang menentukan akses khusus apa yang tersedia untuk entri oleh aplikasi. LDAP mendukung tiga jenis keamanan yang berbeda:

  • Sederhana: Otentikasi dengan cepat menggunakan nama pengguna dan sandi teks biasa.
  • SSL: Mengautentikasi dengan enkripsi SSL melalui jaringan.
  • SASL: Menggunakan mekanisme MD5 / Kerberos. SASL adalah otentikasi sederhana dan skema berbasis lapisan keamanan

Klien mengautentikasi dirinya sendiri ke server dengan menentukan nilai untuk variabel lingkungan yang berbeda di Contextantarmuka, seperti yang terlihat di bawah ini:

Hashtable env = new Hashtable(); env.put(Context.INITIAL_CONTEXT_FACTORY,"com.sun.jndi.ldap.LdapCtxFactory"); env.put(Context.PROVIDER_URL, "ldap://localhost:389"); env.put(Context.SECURITY_AUTHENTICATION,"simple"); env.put(Context.SECURITY_PRINCIPAL,"cn=Directory Manager"); // specify the username env.put(Context.SECURITY_CREDENTIALS,"password"); // specify the password DirContext ctx = new InitialDirContext(env); 

Add new entries in the LDAP server: The options

The LDAP directory server can act as a repository for Java objects. JNDI provides an object-oriented view of this directory, which means that Java objects can be added to and retrieved from the directory without the client needing to manage data representation issues.

Objects can be stored in three ways:

  • Store the Java objects themselves
  • Store a reference to the object
  • Store information as attributes

Let's take a look at each of these in more detail.

Store the Java objects themselves

If a class implements the java.io.Serializable interface, it can be serialized and deserialized from storage media. If we need a simple name-object binding (as in the RMI registry), then the Context.bind() method can store the object. But if we need the more powerful technique of associating attributes with the stored object, we'd employ the DirConext.bind() method instead. Whichever method we use, the object's state is serialized and stored in the server:

MyObject obj = new MyObject(); ctx.bind("cn=anobject", obj); 

Once stored, we can retrieve the object by looking up its name in the directory:

MyObject obj = (MyObject)ctx.lookup("cn=anobject"); 

When an application serializes an object by writing it to an object stream, it records information that identifies the object's class in the serialized stream. However, the class's definition, which is contained in the classfile, is not itself recorded. The system that deserializes the object is responsible for determining how to locate and load the necessary class files.

Alternatively, the application can record the codebase with the serialized object in the directory, either when the binding occurs or by subsequently adding an attribute using DirContext.modifyAttributes(). (We'll examine this second technique later in this article.) Any attribute can record the codebase as long as the application reading back the object is aware of the attribute name. As another option, we can employ the attribute "javaCodebase" specified in the LDAP schema for storing Java objects if schema checking is enabled on the server.

The above example can be modified to supply a codebase attribute containing the location of the MyObject class definition:

// Create object to be bound MyObject obj = new MyObject(); // Perform bind and specify codebase BasicAttribytes battr = new BasicAttributes("javaCodebase","//myserver.com/classes") ctx.bind("cn=anobject", obj, battr); 

AddSeriaize.java demonstrates how to add 10 instances of java.util.Vector, which implements java.io.Serializable; the result can be seen in Figure 1.

Store a reference to the object

Instead of storing the entire serialized state of an object, you can store a reference to that object instead. JNDI's javax.naming.Reference class records address information about objects not directly bound to the directory service. The reference to an object contains the following information:

  • The class name of the referenced object
  • A vector of javax.naming.RefAddr objects that represents the addresses
  • The name and location of the object factory to use during reconstruction

The javax.naming.RefAddr abstract class, seen in Figure 2 below, contains information indicating the ways in which you can contact the object (e.g., via a location in memory, a lookup on another machine, etc.) or recreate it with the same state. The class defines an association between content and type. The content (an object) stores information required to rebuild the object and the type (a string) identifies the purpose of the content.

RefAddr also overrides the java.lang.Object.equals(Object obj) and java.lang.Object.hashcode() methods to ensure that two references are equal if the content and type are equal. RefAddr has two concrete subclasses: javax.naming.StringRefAddr, which stores strings, and javax.naming.BinaryRefAddr, which stores an array of bytes. For example, a string reference address could be an IP, URL, hostname, or something similar.

Consider the example of a referenceable Apartment class. The ADDReference.java example creates a few instances of Apartment and stores them in the server. What happens internally? Since the object is referenceable, a reference is stored and not the serialized object. When the example tries to look up an apartment belonging to styagi, it gets a reference from the server that contains information about the factory class needed, the apartment size, and its location. It then requests that the factory create an Apartment object with the right size and location and return that object. All this happens transparently to the user.

Context ctx = new InitialContext(env);

ctx.bind("apartment=styagi,ou=JavaObjects,o=myserver.com",new Apartment("studio","Mill Complex"));

ctx.bind("apartment=mojoe,ou=JavaObjects,o=myserver.com", new Apartment("2 room","Farm House Apartments"));

ctx.bind("apartment=janedoe,ou=JavaObjects,o=myserver.com",new Apartment("1 room","Pond Side"));

ctx.bind("apartment=rogerp,ou=JavaObjects,o=myserver.com", new Apartment("3 room","Mill Complex"));

ctx.bind("apartment=jamesm,ou=JavaObjects,o=myserver.com", new Apartment("studio","Fox Hill Apartments"));

ctx.bind("apartment=paulh,ou=JavaObjects,o=myserver.com", new Apartment("duplex","Woodbridge"));

ctx.bind("apartment=vkevink,ou=JavaObjects,o=myserver.com",new Apartment("1 room","Woodgate Apartments"));

Apartment apt = (Apartment)ctx.lookup("apartment=styagi,ou= JavaObjects,o=myserver.com");

System.out.println(apt);

The Apartment class would look something like:

public class Apartment implements Referenceable{ private String size; public String location; public Apartment(String size,String location){ this.size=size; this.location=location; } public Reference getReference() throws NamingException{ String classname = Apartment.class.getName(); StringRefAddr classref = new StringRefAddr("Apartment details", size+ ":" +location); String classfactoryname=ApartmentFactory.class.getName(); Reference ref = new Reference(classname,classref,classfactoryname,null); return ref; } public String toString(){ return ("This apartment is "+size+ " and is located at " +location); } } 

Pabrik yang digunakan untuk membuat ulang Apartmentobjek,, ApartmentFactorydisimpan di Reference, seperti yang ditunjukkan di atas. The ApartmentFactoryalat yang javax.naming.spi.ObjectFactoryantarmuka, yang berisi getObjectInstance()metode yang mengembalikan objek baru dibangun berdasarkan referensi, seperti yang terlihat di bawah ini: