| By Berndt Hamboeck | Article Rating: |
|
| July 1, 2002 12:00 AM EDT | Reads: |
8,240 |
In Part 1 (PBDJ, Vol. 9, issue 6) I discussed directory services and how they provide authentication, access control, and finder services for our application. In Part 2, I explain how the JNDI API lets us easily use LDAP.
JNDI Overview
The Java Naming and Directory Interface (JNDI) is an
application programming interface (API) that provides naming and
directory functionality to Java applications. It's independent of any
specific directory service implementation, thus a variety of
directories - new, emerging, and already deployed - can be accessed
the same way.
JNDI Architecture
The JNDI architecture consists of an API and a service
provider interface (SPI). Java applications use the JNDI API to
access a variety of naming and directory services. The SPI enables
these services to be plugged in transparently, allowing the Java
application using the JNDI API to access their services (see Figure
1).
The JNDI class libraries are already included in the Java 2 SDK v1.3, which comes with service providers for LDAP, COS naming, and the RMI registry, and in the rt.jar. If you're using an earlier version of the SDK (with EAServer), you can use the JNDI software, available on the JNDI Web site (at the time of writing, the most current file was ldap-1_2_4.zip; put the file lib/ldapbp.jar into your %Jaguar%/java/lib directory). The JNDI API is a generic API for accessing any naming or directory service. Actual access is enabled by plugging in a service provider under the JNDI. A service provider is software that maps the JNDI API to actual calls to the naming or directory server.
Typically, the roles of the service provider and the naming/directory server differ. In the terminology of client/server software, the JNDI and the service provider are the client (called the JNDI client) and the naming/directory server is the server.
Clients and servers can interact in many ways, e.g., using a network protocol so the client and server can exist autonomously in a networked environment. The server typically supports many different clients, not only JNDI clients, provided the clients conform to the specified protocol. The JNDI does not dictate any particular style of interaction between JNDI clients and servers. For example, at one extreme the client and server could be the same entity.
We need to obtain the classes for the service providers we'll be using. In our example, we plan on using the JNDI to access an LDAP directory server, so we need the software for an LDAP service provider.
Once we have obtained the service provider software, we need to set up or have access to a corresponding naming/directory server. Setting up a naming/directory server is typically the job of a network system administrator. Different vendors have different installation procedures for their servers. Some require special machine privileges before the server can be installed, so consult the naming/directory server software's installation instructions. In this example we're using OpenLDAP and this is (hopefully) still up and running.
The Parameters to Pass
The PowerBuilder client talking to our EAServer component
will pass different parameters to the component, depending on the
actual function call the client is invoking.
We create the classes ldapParam (see Listing 1) and ldapAttrs (see Listing 2). The first one (ldapParam) holds the values to connect to the LDAP server:
- LDAPUser: The user that connects to the server
- LDAPPwd: The password for the user
- LDAPHost: The LDAP host machine's address
- LDAPPort: The port the LDAP server listens on (default: 389)
- LDAPDN: The distinguished name for connecting
- LDAPAttrNames: Not used at the moment ldapAttrs hold the values for making changes on our LDAP server:
- attrName: The attribute we will change/add
- attrValues: The value(s) for the attribute
Then we need a class that holds the return values we want to send back to our client (in this case, PowerBuilder) (see Listing 3).
- ldapsuccess: True, if the last action was successful
- errorString: If there was an error, we pass back the error information
- LDAPAttrib: Attributes we want to send back
- LDAPValue: Values for attributes
Business Functions
In the Remote Interface (see Listing 4) we implement four functions:
1. ldapAuthenticate: Connect to the LDAP server by username/password verification
2. ldapSearch: Search for DN and return the attributes
3. ldapChange: Change attributes on a DN
4. ldapAdd: Add a DN with the given attributes
The ldapAuthenticate Function
In the ldapAuthenticate function we'll pass the information
that's necessary to connect to the LDAP server (see Listing 5).
Before performing any operation on a naming or directory service, we
need to acquire an initial context, so this is the starting point
into the namespace. This is because all methods on naming and
directory services are performed relative to some context. To get an
initial context, follow these steps:
1. Select the service provider of the corresponding service you
want to access.
Specify the service provider to use for the initial context by creating a set of environment properties (a Hashtable) and adding the name of the service provider class to it. We are using the LDAP service provider from Sun Microsystems, so the code looks like the following:
Hashtable env = new Hashtable();
env.put(Context.INITIAL_CONTEXT_FACTORY,
"com.sun.jndi.ldap.LdapCtxFactory");
2. Specify any configuration that the initial context needs.
Clients need a variety of information to contact the directory. For example, we need to specify on which machine the server is running and what information is necessary to identify the user to the directory. Such information is passed to the service provider via environment properties. The LDAP service provider requires that the program specifies the location of the LDAP server (IP address and port), as well as user identity information. The following code provides this information:
env.put(Context.PROVIDER_URL, "ldap://" +
ldapparam.LDAPHost+ ":" + ldapparam.LDAPPort);
env.put(Context.SECURITY_AUTHENTICATION, "simple");
env.put(Context.SECURITY_PRINCIPAL, ldapparam.LDAPUser);
env.put(Context.SECURITY_CREDENTIALS, ldapparam.LDAPPwd);
3. Call the initial context constructor.
We're now ready to create the initial context. To do that,
pass the environment properties that were created earlier to the
InitialContext constructor:
DirContext ctx = new InitialContext(env);
If all is fine, this statement then returns a reference to a DirContext that we'll use when we do different operations on the LDAP directory, such as searching for, adding, or deleting attributes. If there's a problem (either the server could not be found or the username/password verification failed) we'll catch the exceptions and return a message back to the PowerBuilder client.
The ldapSearch Function
One of the most useful features a directory offers is its
yellow pages or search service. You can compose a query consisting of
the attributes of the entries that you're seeking and submit that
query to the directory. The directory then returns a list of entries
that satisfy the query.
In Listing 6 we perform a search with a search base and a search filter (though we don't really need the filter). A search filter is a search query expressed in the form of a logical expression. The syntax of search filters accepted by DirContext.search() is described in RFC 2254 and is basically a logical expression in prefix notation (i.e., the logical operator appears before its arguments).
Note that in most LDAP APIs, we must specify the scope as a parameter in the LDAP search method. This defines exactly how much of the tree we want to search. There are three levels of scope:
1. OBJECT_SCOPE: Searches the base entry; useful if we want to
get the attributes/values of one entry
2. ONELEVEL_SCOPE: Searches the children of the search base, but
not any of its grandchildren
3. SUBTREE_SCOPE: Starts at the base entry and searches
everything below it, including the base entry
Listing 6 uses OBJECT_SCOPE and returns all attributes associated with the entry that satisfies the specified filter (in fact, all our entries have an objectclass).
constraints.setSearchScope(SearchControls.OBJECT_SCOPE);
To select the attributes to return, set the search controls argument. Create an array of attribute identifiers to include in the result and pass it to SearchControls.setReturningAttributes(). For example, to return just the attribute sn write:
String[] attrIDs = {"sn"};
constraints.setReturningAttributes(attrIDs);
To perform the search we call:
NamingEnumeration results =
ctx.search(ldapparam.LDAPDN,
"(&(objectclass=*))", constraints);
Each element in a NamingEnumeration object will contain a SearchResult object that we can retrieve like this:
SearchResult si = (SearchResult)results.next();
We can get the DN of an entry like this:
System.out.println("name: " + si.getName());
To get the attributes of an entry we use the getAttributes() method of the SearchResult class:
Attributes attrs = si.getAttributes();
Now we can step through the attributes, read the values, and store them (first in a vector) in order to return them (using a string array) to the client.
To test the login, use the PowerBuilder client. Enter the values for the OpenLDAP administrator or provide these values (see Listing 1 from Part 1):
uid=berham, ou=developer, o=myorg, c=US ham1
uid=betha, ou=developer, o=myorg, c=US betha1
Click the Test Login button and you should get a MessageBox indicating a successful login or an errorstring that tells you what went wrong.
The ldapChange Function
One way to modify the attributes of an object is to supply a
list of modification requests (ModificationItem). Each
ModificationItem consists of a numeric constant that indicates the
type of modification to make and an Attribute that describes the
modification (see Listing 7). (Due to space constraints, Listings 7
and 8 can be downloaded from www.sys-con.com/pbdj/sourcec.cfm.)
Following are the three types of modifications:
1. ADD_ATTRIBUTE
2. REPLACE_ATTRIBUTE
3. REMOVE_ATTRIBUTE
Modifications are applied in the order in which they appear in the list. Either all or none of the modifications are executed. Use a for loop to get all attributes and change one value (if you want to have more than one value for an attribute, add a second for loop).
ModificationItem[] mods = new ModificationItem[ldapattrs.length];
for(int valNr = 0; valNr < ldapattrs.length; valNr++) { Attribute mod0 = new
BasicAttribute(ldapattrs[valNr].attrName,
ldapattrs[valNr].attrValues[0]);
}
After creating this list of modifications, we can supply it to modifyAttributes() as follows:
ctx.modifyAttributes(ldapparam.LDAPDN, mods);
To test the new function, run the PowerBuilder client and click the Read button; the output is on the left (see Figure 2). Clicking the change button changes my former girlfriend's status to that of wife (see Figure 3). This means that sn and cn attributes have changed. To see it click the Clear button and again the Read button.
The interesting part behind the Change button is these lines:
String cn_values[] = { "Hamboeck"};
String sn_values[] = { "Bettina Hamboeck" };
lstr_attrs[1].attrname = "cn"
lstr_attrs[1].attrvalues = cn_values
lstr_attrs[2].attrname = "sn"
lstr_attrs[2].attrvalues = sn_values
lstr_return = ldap.ldapChange(lstr_param, lstr_attrs)
We set the attribute's name and a new value and pass this to the ldapChange function in our EJB.
The ldapAdd Function
This is very similar to the ldapAdd function (see Listing 8).
I've implemented that an attribute can have more than one value, so
we have two for loops here.
The createSubcontext function creates a new subcontext with the given name, binds it in the target context (named by all but the terminal atomic component of the name), and associates the supplied attributes with the newly created object.
ctx.createSubcontext(ldapparam.LDAPDN, orig);
To test our new function, run the PowerBuilder client and click the Add button. Verify that this new developer is in the LDAP directory with the Read button (by copying the DN to the read singlelinedit) or the LDAP browser.
Summary
Directory services play an important role in the network
environment. They provide authentication, access control, and finder
services for the applications. The JNDI API allows us to use LDAP
easily, and with EAServer we can provide access to different clients
and use the same business logic in different client applications.
Resources
- Sun's JNDI tutorial: http://java.sun.com/products/
jndi/tutorial/basics/index.html - JNDI-LDAP classes: http://java.sun.com/products/jndi/
serviceproviders.html - SLAPD and SLURPD Documentation: www.umich.edu/~dirsvcs/ldap/doc/
- The SLAPD and SLURPD Administrators Guide: www.umich.edu/~dirsvcs/ldap/doc/guides/slapd/
- The LDAP Data Interchange Format (LDIF) - Technical Specification: www.faqs.org/rfcs/rfc2849.html
- LDAP C++ class: www.many-monkeys.co.uk/ldap.htm
Published July 1, 2002 Reads 8,240
Copyright © 2002 SYS-CON Media, Inc. — All Rights Reserved.
Syndicated stories and blog feeds, all rights reserved by the author.
More Stories By Berndt Hamboeck
Berndt Hamboeck is a senior consultant for BHITCON (www.bhitcon.net). He's a CSI, SCAPC8, EASAC, SCJP2, and started his Sybase development using PB5. You can reach him under admin@bhitcon.net.
- Why SOA Needs Cloud Computing - Part 1
- Cloud Expo and The End of Tech Recession
- The Transition to Cloud Computing: What Does It Mean For You?
- A Rules Engine Built in PowerBuilder
- Sybase Named “Silver Sponsor” of iPhone Developer Summit
- How PowerBuilder Got Its Groove Back
- The Cloud Has Cross-Border Ambitions
- Ulitzer Names The World's 30 Most Influential Virtualization Bloggers
- Ulitzer Named "New Media" Partner of Greatly Anticipated iStrategy Event in Berlin
- Risks and Enterprise Mobility?
- Steps for Success in Enterprise Mobility?
- Are Mobile Luddites Resisting Mobility?
- The Difference Between Web Hosting and Cloud Computing
- Sybase CTO to Speak at 4th International Cloud Computing Expo
- Why SOA Needs Cloud Computing - Part 1
- Cloud Expo and The End of Tech Recession
- The Transition to Cloud Computing: What Does It Mean For You?
- Five Reasons to Choose a Private Cloud
- Seeding The Cloud: The Future of Data Management
- The Threat Behind the Firewall
- Economy Drives Adoption of Virtual Lab Technology
- Tips for Efficient PaaS Application Design
- A Rules Engine Built in PowerBuilder
- Sybase Named “Silver Sponsor” of iPhone Developer Summit
- Where Are RIA Technologies Headed in 2008?
- PowerBuilder History - How Did It Evolve?
- The Top 250 Players in the Cloud Computing Ecosystem
- Custom Common Dialogs Using SetWindowsHookEx
- DDDW Tips and Tricks
- OLE - Extending the Capabilities of PowerBuilder
- DataWindow.NET How To: Data Entry Form
- Book Excerpt: Sybase Adaptive Server Anywhere
- Sybase ASE 12.5 Performance and Tuning
- Working with SOA & Web Services in PowerBuilder
- Office 2003 Toolbar: A New Look For Your Old PowerBuilder App
- Dynamically Creating DataWindow Objects
































