Payara Server LDAP Integration - Part 2: Configuring Security

Photo of Fabio Turizo by Fabio Turizo

See 'Part 1 - Configuring the LDAP Server' here.

 

In this three-parts article series 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.

 

 

In Part 1, I showed you how to start 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

 

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 attribute (uniquemember).

What happens if you want to fine tune the directory searches and filters specifically for your organization? 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 organization'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 an index.xhtml landing page that will simply print out the UID/Username of the authenticated user:

 

<?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}!
        <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>

 

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:

 

<?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>For Admins Only</title>
    </h:head>
    <h:body>
        Welcome #{welcomeBean.user}! Since you are an administrator, you can access this page
    </h:body>
</html>

 

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

 

<?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>For Everyone Actually</title>
    </h:head>
    <h:body>
        Welcome #{welcomeBean.user}! You are in the common group, so you can see this page.
    </h:body>
</html>

 

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:

 

<?xml version="1.0" encoding="UTF-8"?>
<web-app version="3.1" xmlns="http://xmlns.jcp.org/xml/ns/javaee" 
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" 
xsi:schemaLocation="http://xmlns.jcp.org/xml/ns/javaee 
http://xmlns.jcp.org/xml/ns/javaee/web-app_3_1.xsd">
    <context-param>
        <param-name>javax.faces.PROJECT_STAGE</param-name>
        <param-value>Development</param-value>
    </context-param>
    <servlet>
        <servlet-name>Faces Servlet</servlet-name>
        <servlet-class>javax.faces.webapp.FacesServlet</servlet-class>
        <load-on-startup>1</load-on-startup>
    </servlet>
    <servlet-mapping>
        <servlet-name>Faces Servlet</servlet-name>
        <url-pattern>*.xhtml</url-pattern>
    </servlet-mapping>
    <welcome-file-list>
        <welcome-file>index.xhtml</welcome-file>
    </welcome-file-list>
    <security-constraint>
        <display-name>General Security</display-name>
        <web-resource-collection>
            <web-resource-name>All</web-resource-name>
            <url-pattern>/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
            <role-name>COMMON_USER</role-name>
        </auth-constraint>
    </security-constraint>
    <security-constraint>
        <display-name>Only for administrators</display-name>
        <web-resource-collection>
            <web-resource-name>Admin</web-resource-name>
            <url-pattern>/admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <role-name>ADMIN</role-name>
        </auth-constraint>
    </security-constraint>
    <login-config>
        <auth-method>BASIC</auth-method>
        <realm-name>userDirectoryRealm</realm-name>
    </login-config>
    <session-config>
        <session-timeout>5</session-timeout>
    </session-config>
</web-app>

 

 

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 since BASIC is an insecure option.
  • In the security-constraint section we are distributing the access to the 3 pages we created 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 glassfish-web.xml file:

 

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE glassfish-web-app PUBLIC "-//GlassFish.org//DTD GlassFish Application Server 3.1 Servlet 3.0//EN" "http://glassfish.org/dtds/glassfish-web-app_3_0-1.dtd">
<glassfish-web-app error-url="">
  <class-loader delegate="true"/>
  <context-root>/ldap-test</context-root>
  <security-role-mapping>
    <group-name>Admins</group-name>
    <role-name>ADMIN</role-name>
  </security-role-mapping>
  <security-role-mapping>
    <group-name>Common</group-name>
    <role-name>COMMON_USER</role-name>
  </security-role-mapping>
</glassfish-web-app>

 

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 our WAR 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 input cbeta/cbeta):

 

2-ldap-web-test-1.jpg

 

 

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

 

3-ldap-web-test-2.jpg

 

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:

 

4-ldap-web-test-3.jpg

 

5-ldap-web-test-4.jpg

See 'Part 3 : Extracting User Information' here.

 

 

Comments