User Guide

Integrating LDAP with Payara® Server

The Payara® Platform - Production-Ready, Cloud Native and Aggressively Compatible. Integrating LDAP with Payara® Server

Contents

Starting the LDAP Server 1 Configuring Security 5 Creating a Web Application 6 Extracting User Information 11 Seamless Authentication and Authorization 17 About Payara Services, Ltd 18 Integrating LDAP with Payara® Server

If you work in an organization with a robust IT department, it’s very likely that you are using a LDAP server to handle your user directory information. You probably have to follow some general guidelines dictating that all web applications deployed within the organization’s infrastructure must access this user directory; and must authenticate and authorize the users that will interact with them. This is a very common scenario nowadays.

In this guide, I will illustrate 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.

Starting the LDAP Server

There are many different LDAP server implementations in the market today (in both commercial and open source models). For our scenario, we will quickly start an OpenDJ instance using a Docker container and set up a directory domain with some test users and groups.

First, we start with a new Docker image that will download the OpenDJ binaries and run them in a container. This is a based image, since OpenDJ needs the JDK to run:

FROM java:8 MAINTAINER Fabio Turizo WORKDIR /opt ENV JAVA_HOME /usr/lib/jvm/java-8--amd64/ ENV MVN_REPO=https://maven.forgerock.org/repo/repo/org/forgerock/opendj ENV OPENDJ_VERSION=3.0.0 ENV SERVER_PATH opendj-server-legacy RUN curl $MVN_REPO/$SERVER_PATH/$OPENDJ_VERSION/$SERVER_PATH $OPENDJ_VERSION. zip \ -o /tmp/opendj.zip && \ unzip /tmp/opendj.zip -d /opt && \ rm /tmp/opendj.zip ADD run.sh /opt/opendj/run.sh ADD users.ldif /opt/opendj/initial.ldif EXPOSE 1389 4444 WORKDIR /opt/opendj CMD ["./run.sh"]

1 Integrating LDAP with Payara® Server

You will notice that we are using 2 external files in this image:run.sh and users.ldif. Let’s start with the users.ldif file, which we are using to create a starting set of users and groups:

dn: dc=payara,dc=fish objectClass: top objectClass: domain dc: payara

dn: cn=Alfa Michael,dc=payara,dc=fish objectClass: organizationalPerson objectClass: top objectClass: person objectClass: inetOrgPerson uid: malfa mail: [email protected] givenName: Michael sn: Alfa cn: Alfa Michael userPassword: {SSHA}nirDyc9/XKLXqUqlR3sqD1De3qhybUqZQeU8pg== creatorsName: cn=Directory Manager,cn=Root DNs,cn=config

dn: cn=Beta Carol,dc=payara,dc=fish objectClass: organizationalPerson objectClass: top objectClass: person objectClass: inetOrgPerson uid: cbeta mail: [email protected] givenName: Carol sn: Beta cn: Beta Carol userPassword: {SSHA}ALhq+r+G3znVsPH70FkzyhHRZiN092w1GXiAZw==

dn: cn=Omega John,dc=payara,dc=fish objectClass: organizationalPerson objectClass: top objectClass: person objectClass: inetOrgPerson uid: jomega mail: [email protected] givenName: John

2 Integrating LDAP with Payara® Server

sn: Omega cn: Omega John userPassword: {SSHA}KVj0XDak6E+IRecFkkCveTzsmW014IlGN2LlWg==

dn: cn=Admins,dc=payara,dc=fish objectClass: groupOfNames objectClass: top member: cn=Alfa Michael,dc=payara,dc=fish description: Administrators cn: Admins entryUUID: b7f3af29-3834-4765-9978-33e419073a65 createTimestamp: 20161019012425Z creatorsName: cn=Directory Manager,cn=Root DNs,cn=config

dn: cn=Common,dc=payara,dc=fish objectClass: groupOfNames objectClass: top member: cn=Beta Carol,dc=payara,dc=fish member: cn=Omega John,dc=payara,dc=fish description: Common Users cn: Common entryUUID: 8bc4ac5c-3313-4f9d-a111-6c933191fb2d createTimestamp: 20161019012453Z creatorsName: cn=Directory Manager,cn=Root DNs,cn=config

The contents of this file will allow us to create an initial set of 3 users (Michal Alfa, Carol Beta and John Omega) and 2 groups (Admin and Common). These objects are under the dc=payara, dc=fish base domain name.

Finally, we have the run.sh file. This file handles the OpenDJ installation and initialization using bash scripting:

#!/usr/bin/env bash cd /opt/opendj/

if [ ! -d ./data/config ] ; then echo "Executing OpenDJ first setup"

MANAGER_USER=${MANAGER:-"cn=Directory Manager"} BASE_DN=${BASE_DN:-"dc=payara,dc=fish"} PASSWORD=${PW:-admin}

3 Integrating LDAP with Payara® Server

./setup --cli --hostname localhost --ldapPort 1389 --rootUserDN "${MANAGER_ USER}" \ --rootUserPassword "${PASSWORD}" --backendType pdb --baseDN "${BASE_DN}" \ --ldifFile "/opt/opendj/initial.ldif" --acceptLicense --no-prompt \ --noPropertiesFile else echo "Starting OpenDJ" ./bin/start-ds fi

if (bin/status -n -w "${PASSWORD}" | grep Started); then echo "OpenDJ is running" while true; do sleep 100000; done fi

This bash script will detect if there’s a previous OpenDJ installation (by checking out if the local data directory ./data/config exists). If not, it will setup OpenDJ using the command line interface option of the setup binary utility. The script provides the values for the installation options (root user, root password, LDAP port, etc.), but some of them can be changed with environment variables (${MANAGER}, ${PASSWORD}, etc.). If OpenDJ is already installed, then the script will simply start the server.

Finally, we let the script run the container indefinitely by starting an infinite loop that sleeps the input at frequent intervals.

Now, we proceed to build this image:

docker build -t fturizo/opendj .

And then start a new container with it:

docker run -d -p 1389:1389 -v ~/opendj-data:/opt/opendj/data –-name=opendj fturizo/opendj

4 Integrating LDAP with Payara® Server

You can now connect to this LDAP server using port 1389. Using an LDAP Browser tool, we can check that our schema was imported correctly and the OpenDJ server is running:

Configuring Security

After starting the LDAP Server - now we need to configure a new LDAP security realm in our Payara Server instance for our Java EE application to connect to the user directory through the JAAS (Java Authentication and Authorization Services) API. With a Payara Server domain running, we execute the following command:

create-auth-realm --classname=com.sun.enterprise.security.auth.realm.ldap. LDAPRealm \ --property=jaas-context=ldapRealm:\ base-dn="dc=payara, dc=fish":\ directory="ldap://192.168.99.100:1389" \ group-search-filter="member=%d" \ --target=server-config userDirectoryRealm

5 Integrating LDAP with Payara® Server

With this command, we’re creating a new LDAP security realm called userDirectoryRealm that will authenticate and authorize both users and groups for our Java EE web application. You will notice that we are setting the following properties:

• Base DN: We set this property to the base directory name of our LDAP, in this case dc=payara, dc=fish. • Directory: This property points to the location of our LDAP server. Pay attention to the port that we are using (1389). • Group Search Filter: With this property, we’re overriding the default search filter query that the realm uses to identify which users are part of a group. Since OpenDJ uses the object attribute member, we’re setting the query to use it instead of the default configured attrib- ute (uniquemember). What happens if you want to fine tune the directory searches and filters specifically for your organ- ization? You can use the following additional properties:

• group-base-dn: set this property to the base directory name - the realm will use it to read the group data. For example: cn=Organization Groups would imply that all the organi- zation’s groups live in this directory object. • search-bind-dn: set this property to the directory name of an administrator user the realm can bind to in case your LDAP server doesn’t allow anonymous binding (in our case, the default setting of OpenDJ is to indeed allow anonymous binding). • search-bind-password: the password of the DN set in the search-bind-dn property • search-filter: you can use this property to customize the search query used to locate a user in the directory tree. For example: ou=People, uid=%s would limit the search to all users under the People organization unit object.

Creating a Web Application

Next, we proceed to create a sample web application to test out our LDAP configuration. For this application, we will create 3 sample JSF pages:

1 - Our first page will be anindex.xhtml landing page that will simply print out the UID/Username of the authenticated user:

LDAP Test

6 Integrating LDAP with Payara® Server

Welcome #{welcomeBean.user}!

Notice that we are using a bean of name WelcomeBean to get the username (more on this bean later). We also are setting some navigation links to access the other two pages.

2 - Another page, admin/index.xhtml (under folder admin), that only users that belong to the Admins group can access:

For Admins Only Welcome #{welcomeBean.user}! Since you are an administrator, you can access this page

3 - A finalcommon/index.xhtml page (under folder common) that both common users and admin- istrators can access:

For Everyone Actually

7 Integrating LDAP with Payara® Server

Welcome #{welcomeBean.user}! You are in the common group, so you can see this page.

4 - Finally, we create our WelcomeBean CDI component to get the username:

@RequestScoped @Named public class WelcomeBean {

@Inject Principal principal;

public String getUser(){ return principal.getName(); } }

We are retrieving the username with an injected Principal object courtesy of the CDI-JAAS bridge integration included with Java EE.

Now that we have our code artifacts, we will proceed to configure our application’s security constraints. First, we configure authorization access for these pages and authentication in the web.xml file:

javax.faces.PROJECT_STAGE Development Faces Servlet javax.faces.webapp.FacesServlet

8 Integrating LDAP with Payara® Server

1 Faces Servlet *.xhtml index.xhtml General Security All /* ADMIN COMMON_USER Only for administrators Admin /admin/* ADMIN BASIC userDirectoryRealm 5

9 Integrating LDAP with Payara® Server

Notice the following:

• In the login-config session we are configuring how our application will be authenticating users: using BASIC authentication against the LDAP realm we created. In a production environment, you may choose a more robust auth-method configuration sinceBASIC is an insecure option. • In the security-constraint section we are distributing the access to the 3 pages we cre- ated to the 2 roles ADMIN and COMMON_USER. Keep in mind that these 2 roles MUST be mapped to the corresponding groups that are registered in the LDAP server.

Finally, we configure the role mappings in the -web.xml file:

Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1. dtd"> /ldap-test Admins ADMIN Common COMMON_USER

With this configuration, whenever Payara Server checks the authorization access permissions of a user against the LDAP server, it will use the mappings defined to check the correct group based on its roles.

Now that we have configured our application, we can just simply build ourWAR file and deploy it to Payara Server (I recommend using Maven for this step). To test that our scenario works as intended we head to the landing page first (the browser will ask for credentials so we will inputcbeta/cbeta ):

10 Integrating LDAP with Payara® Server

Now, if we navigate to the administrator’s page:

Since user cbeta doesn’t belong to the Admins group, the server will deny access to the page by responding with a 403 Forbidden error page. This is to be expected in accordance to the security constraints set earlier.

If you execute the same tests with user malfa, then access will be granted to both pages:

Extracting User Information

So far, you’ve started the LDAP Server and configured the LDAP realm. Now you are probably won- dering 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:

11 Integrating LDAP with Payara® Server

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:

org.forgerock.opendj opendj-ldap-sdk 2.6.11

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

forgerock-staging-repository ForgeRock Release Repository https://maven.forgerock.org/repo/releases false

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 {

12 Integrating LDAP with Payara® Server

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

@Inject private Instance 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"); } } }

13 Integrating LDAP with Payara® Server

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() {

14 Integrating LDAP with Payara® Server

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;

15 Integrating LDAP with Payara® Server

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:

LDAP Test Welcome #{welcomeBean.user.fullName}!
First Name: #{welcomeBean.user.firstName}, Last Name: #{welcomeBean. user.lastName}
Email: #{welcomeBean.user.email}

16 Integrating LDAP with Payara® Server

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

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.

Seamless Authentication and Authorization

Integrating Payara Server with a LDAP directory is quite easy. You must be acquainted with the man- ner 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 prop- erties 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.

17 Integrating LDAP with Payara® Server

About Payara Services, Ltd

We are a dedicated team of professionals devoted to Open Source, Java, our customers, and the community. We are major contributors to the development and engineering effort of the Payara Server Open Source Project and the Payara Foundation.

Our global team of Engineers delivers 24/7 production, development & migration support directly to our customers worldwide. When our customers need support – they get it straight from the engineers rather than an outsourced help desk employee.

As Strategic Members of the Foundation, we invest our resources and expertise to improve, innovate and develop Open Source technologies. We are involved in shaping the future of the industry via our direct contribution to Eclipse EE4J (Payara’s Director, Steve Millidge, is a Project Management Committee member), Eclipse MicroProfile®, Eclipse IDE and many other projects.

We love to share Payara Server with the world, and when our commitment to continuous develop- ment and support of the Open Source software is combined with contributions from our community, we can ensure the highest quality solution. Let’s continue to learn, create and shape the future of the industry together.    

  

[email protected] +44 207 754 0481 www.payara.fish

Payara Services Ltd 2016 All Rights Reserved. Registered in England and Wales; Registration Number 09998946 Registered Office: Malvern Hills Science Park, Geraldine Road, Malvern, United Kingdom, WR14 3SZ

18