Payara Server LDAP Integration - Part 3: Extracting User Information

15 Nov 2016

In this three-parts article series I'm illustrating the implementation of the LDAP integration using a sample scenario: integrate Payara Server with a LDAP user directory and manage the authentication and authorization of a sample web application.

 

In Part 1, I showed you how to start the LDAP Server, while in Part 2  we configured the LDAP realm. Now you are probably wondering how to get the user’s information (first and last name, email address, etc.) that resides in the LDAP server. Unfortunately, the JAAS API doesn’t offer any standard mechanisms to access this user attributes in the directory tree. But there are other options available:

 

  1. Write your own custom LDAP realm that extends the com.sun.enterprise.security.auth.realm.ldap.LDAPRealm class and extract the user’s information into the current HTTP request. This can be difficult to code and you need the knowledge on how to configure Payara Server /GlassFish login modules as well.
  2. Use a third-party library to access the LDAP server and get the user’s information depending on the LDAP’s object tree. Personally, I favor this option since it’s easier to implement and offers more flexibility.

For our scenario, we are going to implement a CDI bean service that will use the OpenDJ’s Java SDK to connect to the LDAP server and retrieve the user information. Considering that any other SDK can be also used to complete this task I have chosen the OpenDJ SDK for the sake of consistency. We will add its Maven dependency into our POM file:

 

<dependency>
        <groupId>org.forgerock.opendj</groupId>
        <artifactId>opendj-ldap-sdk</artifactId>
        <version>2.6.11</version>
</dependency>

 

We also need to add the ForgeRock releases repository so Maven can download this dependency correctly:

 

<repositories>
    <repository>
        <id>forgerock-staging-repository</id>
        <name>ForgeRock Release Repository</name>
        <url>https://maven.forgerock.org/repo/releases</url>
        <snapshots>
            <enabled>false</enabled>
        </snapshots>
    </repository>
</repositories>

 

Now, we will implement a service that will retrieve a User object base on the username/principal that is currently authenticated:

 

@ApplicationScoped
public class UserService {

    private static final String LDAP_SERVER_HOST = "192.168.99.100";
    private static final int LDAP_SERVER_PORT = 1389;

    @Inject
    private Instance<Principal> principalInstance;

    private LDAPConnectionFactory connectionFactory;

    @PostConstruct
    public void init() {
        this.connectionFactory = new LDAPConnectionFactory(LDAP_SERVER_HOST, LDAP_SERVER_PORT);
    }

    public User getUser() {
        Principal principal = principalInstance.get();
        if (connectionFactory != null && principal != null) {
            String username = principal.getName();
            try (Connection newConnection = this.connectionFactory.getConnection()) {
                final SearchRequest request = Requests.newSearchRequest(DN.valueOf("dc=payara,dc=fish"), 
                                                                        SearchScope.WHOLE_SUBTREE, 
                                Filter.format("(uid=%s)", username));
                final SearchResultEntry result = newConnection.searchSingleEntry(request);
                return new User(username, result.parseAttribute("givenName").asString(),
                                          result.parseAttribute("sn").asString(),
                                          result.parseAttribute("cn").asString(),
                                          result.parseAttribute("mail").asString());
            } catch (ErrorResultException ex) {
                throw new RuntimeException("Couldn't retrieve the information of user " + username, ex);
            }
        }else{
            throw new RuntimeException("Either the user is not authenticated or the 
      LDAP server cannot be contacted");
        }
    }
}

 

First, notice that our service initializes an instance of a LDAPConnectionFactory. This object will create connections that will execute queries for objects to our LDAP server. These queries are executed in the getUser method, by opening a new connection to the server, creating a new search request using the Requests object and finally passing this request to the searchSingleEntry method of the connection object. The filter query in question is (uid=%s) , which will look for all users that match the supplied UID (username) under the supplied base directory name (dc=payara, dc=fish).

 

If the search cannot find the user in the LDAP server, an ErrorResultException will be thrown by the API to signal this result. Otherwise, if the search is successful, a SearchResultEntry object will be created so we can extract the user’s attributes (CN, SN, GivenName and Mail). This is the code for our User object:

 

public class User {
    private final String username;
    private final String firstName;
    private final String lastName;
    private final String fullName;
    private final String email;

    public User (String username, String firstName, String lastName, String fullName, String email) {
        this.username = username;
        this.firstName = firstName;
        this.lastName = lastName;
        this.fullName = fullName;
        this.email = email;
    }

    public String getUsername() {
        return username;
    }

    public String getFirstName() {
        return firstName;
    }

    public String getLastName() {
        return lastName;
    }

    public String getFullName() {
        return fullName;
    }

    public String getEmail() {
        return email;
    }

    @Override
    public int hashCode() {
        int hash = 7;
        hash = 17 * hash + Objects.hashCode(this.username);
        return hash;
    }

    @Override
    public boolean equals(Object obj) {
        if (this == obj) {
            return true;
        }
        if (obj == null) {
            return false;
        }
        if (getClass() != obj.getClass()) {
            return false;
        }
        final User other = (User) obj;
        return Objects.equals(this.username, other.username);
    }
}

 

Now, we will proceed to update the landing page to show this information appropriately. First, we need to update the WelcomeBean component so it can reference this newly coded service:

 

@RequestScoped
@Named
public class WelcomeBean {

    @Inject
    private UserService userService;

    private User currentUser;

    @PostConstruct
    public void init(){
        this.currentUser = userService.getUser();
    }

    public User getUser(){
        return currentUser;
    }
}

 

Then, we update the index.html file and add the corresponding bean properties in the markup:

 

<?xml version='1.0' encoding='UTF-8' ?>
<!DOCTYPE html>
<html xmlns="http://www.w3.org/1999/xhtml"
      xmlns:h="http://xmlns.jcp.org/jsf/html">
    <h:head>
        <title>LDAP Test</title>
    </h:head>
    <h:body>
        Welcome #{welcomeBean.user.fullName}!
        <br />
        First Name: #{welcomeBean.user.firstName}, Last Name: #{welcomeBean.user.lastName}
        <br/>
        Email: #{welcomeBean.user.email}
        <h:form>
            <h:commandLink value="Go to admins page" action="admin/index.xhtml?faces-redirect=true"/>
            <br/>
            <h:commandLink value="Go to commons page" action="common/index.xhtml?faces-redirect=true"/>
        </h:form>
    </h:body>
</html>

 

And finally, we run our application again to observe the outcome:

 

web-test-5.jpg

 

I’d recommend configuring the UserService component in a production environment so that it retrieves the server’s location (hostname and port) and the base directory name (Base DN) using the custom JNDI resources feature offered by Payara Server /GlassFish. This way the information is decoupled from the code and it also helps in the implementation of separate environments for testing and production scenarios.

 

In summary

Integrating Payara Server with a LDAP directory is quite easy. You must be acquainted with the manner your organization’s user directory is structured but otherwise the task is simple enough and can be done in almost all environments. Using the JAAS API shipped with Java EE, it’s possible to have the server handle authentication and authorization seamlessly and with little changes.

 

If, for example, your organization uses Microsoft’s Active Directory, it’s also easy to change the properties of the security realm to integrate with it, since AD mimics the standard LDAP implementation with little differences. Coupled with the use of a third-party API to access the information stored in the directory, any Java EE application can retrieve additional information and use it to complement its functionality for the better.

 

Payara Server LDAP Integration - Part 1: Configuring the LDAP Server 

Payara Server LDAP Integration - Part 2: Configuring Security

 

 

Comments

Subscribe