Zero Trust Security in Enterprise Java: What it is and How to Implement it
Cybersecurity isn’t just about building walls, fortresses, moats or any other external barrier anymore. Nowadays, it’s important to check […]
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:
dc=payara, dc=fish
.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:
cn=Organization Groups
would imply that all the organization’s groups live in this directory object.ou=People, uid=%s
would limit the search to all users under the People organization unit object.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:
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):
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:
Cybersecurity isn’t just about building walls, fortresses, moats or any other external barrier anymore. Nowadays, it’s important to check […]
We’re excited to announce that Payara Platform Community 7 Beta application server is now fully certified as Jakarta EE 11 […]
Middleware runs quietly in the background of most applications, which makes it easy to overlook its lifecycle. In effect, […]
Hello Fabio,
Thanks for the informative post.
I have a requirement where we I need to establish LDAP based login to Payara server administration console.
I have done the configuration in Security > Realms > LDAP, but I am still not able to login using ldap id.
Could you please share what steps do I need to take in order to enable this?
Thanks,
Hitesh Tambe
Hi, did you find the solution for this ? it would be helpful if we share the fix.
Hi Jaya, check my reply to Hitesh, which unfortunately was posted in the original thread below. If you are still struggling with this issue, I’d recommend getting help in our official Payara Forum here: https://groups.google.com/forum/#!topic/payara-forum/.
Cheers,
Fabio.
Hi Fabio,
I have a very confusing situation where the server “selectively” performs an ldap bind request for some users and not for others.. I’ve captured the traffic between the payara server and the ldap server … the sequence that I see in the “good” case is : searchRequest/Response/Done, bindRequest/Response, searchRequest/Response/Done (group membership) and finally another searchRequest/Response/Done for the user… however in the “bad” cases there is no bindRequest even though the first searchRequest is successful finding the user….
Hi Francisco,
You may find it worthwhile asking over at our google forum where hopefully someone will be able to help you out.
https://groups.google.com/forum/#!forum/payara-forum
Hi Hitesh,
After creating the realm, you have to change the realm used by the Admin Service (by default ‘admin-realm’) and set it to the new realm that you have created. You can do this change in the Admin Console under Configurations -> Server Configuration -> Admin Service. Keep in mind that changing the default realm comes with associated risks since the server would have a direct dependeny on your LDAP server to work correctly.
Cheers,
Fabio.
nice post! however how do you define the password mapping? in other words, the password given in the browser form must match with some password field in LDAP right?
Hi Alessandro.
On Part 1, I specified the initial set of users that will populate the directory. If you observe close enough, you will see that the user passwords are specified as hashed values by using the standard LDAP property `userPassword`. The LDAP specification dictates that any authorization attempts must match against what the configuration of the password is. I recommend reading the documentation on how password storage works for OpenLDAP here: https://www.openldap.org/doc/admin24/security.html
Cheers,
Fabio.
Fabio, sharing the sources is the best way of sharing knowledge.
Hi Marcel, unfortunately, this post is a bit old and I no longer have access to the sources. Replicating the entire codebase following the instructions in the 2 parts should be simple enough.