Glassfish Active Directory Authentication (1)

We can claim that, so far, Java has never let us down. We always knew that Java Enterprise could authenticate against Active Directory (or that writing such a JAAS module would not be too hard). Fifteen years later, we have a use case, and expect many more, since SAMBA/AD/LDAP/Kerebos is the only readily available serious security infrastructure, and apparently Windows compatible.

  • In Part 1, we authenticate web users against an Active Directory without SSL.

Our AD is driven by Samba, but we hear that Windows is a Samba clone, so the process is identical. While relatively very simple, there are a few dangerous curves, some of which are bug related. The primary bug that we encountered concerned DNS resolution on Glassfish.

The method we use in Part 1 is deficient in a number of regards.

  1. The communication between Glassfish and the AD server is in clear-text. While we address this issue in part 2, the application server and AD server should both lie in the back-office, on a separate firewalled subnet from the office and public
  2. In this example, the form-based authentication is BASIC, and hence passwords are in clear-text between the browser and the application server. A more secure certificate method should be used in practise, even on the intranet.

We consider two scenarios.

In the first, the application is implicitly AD related: it must be deployed against AD LDAP. A realm is declared with roles, but this realm is named in such a manner as to alert us that it is AD specific, and the roles are AD natural.

In the second scenario, the application is LDAP independent, a realm with roles is declared, but can be implemented as the deployer wishes: we just choose to use AD LDAP.

Scenario One

In scenario one, we are deploying a third part tool facilitating intranet Web based interaction with an Active Directory system. For example, the tool provides domain users with a facility to change their password, and provides the domain administrators with a facility to manage users.

The delivered war looks something like this.

Screenshot from 2015-09-05 22:12:24

The delivered web.xml file contains

 ...    
    <security-role>
        <role-name>ADUSER</role-name>
    </security-role>

    
    <security-constraint>
        <display-name>Active Directory Users</display-name>
        <web-resource-collection>
            <web-resource-name>Active Directory related Resources</web-resource-name>
            <description />
            <url-pattern>/ad-user/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description />
            <role-name>ADUSER</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <security-role>
        <role-name>ADADMIN</role-name>
    </security-role>


    <security-constraint>
        <display-name>Active Directory Administrators</display-name>
        <web-resource-collection>
            <web-resource-name>ActiveDirectoryAdministrativeResources</web-resource-name>
            <description />
            <url-pattern>/ad-admin/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description />
            <role-name>ADADMIN</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>


    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>ActiveDirectoryRealm</realm-name>
        <form-login-config>
            <form-login-page>/login.xhtml</form-login-page>
            <form-error-page>/error.xhtml</form-error-page>
        </form-login-config>
    </login-config>
...

Scenario One: Deployment

Before beginning you will need the following information.

[ldapAddr] The address of the LDAP server. In our case, this is the same as our PDC. (We currently use the IP-number because of the DNS issue.)
[ldapPort] The LDAP port on which the server listens. In this example we use 389, which is insecure.
[baseDN] The LDAP base DN for the location of user data. A typical AD is identified by

  • [DOMAIN_NAME].[ROOT_NAME]

in which case the base DN is

  • dc=[DOMAIN_NAME],dc=[ROOT_NAME].

Case is unimportant.

(For example "dc=acme,dc=com" or "dc=acme,dc=local".)

[searchUserName] An AD user that Glassfish can use to access the AD. This user does not need to be a member of Administrators.
[searchUserPassword] The password for user [searchUserName].

Open the Glassfish administrative console and browse to Configurations|server-config|Security|Realms.

Screenshot from 2015-09-05 09:53:40

Click on New to create a new realm.

Screenshot from 2015-09-05 09:54:34

Set

Name ActiveDirectoryRealm See web.xml.
Class Name com.sun.enterprise.security.auth.realm.ldap.LDAPRealm Sun LDAPRealm.

Glassfish will then present more options.

Screenshot from 2015-09-05 23:02:41

Set

JAAS Context ldapRealm Must be exactly as appears, and informs JAAS.
Directory ldap://[ldapAddr]:[ldapPort] We use the actual IP-number, given the DNS bug we encounter.
Base DN dc=[DOMAIN_NAME],dc=[ROOT_NAME] Glassfish cannot parse this line. Use something simple like xxx, save and then edit.
Assign Groups AuthenticatedUser This is a place-holder for all authenticated users, needed since a standard user belongs to no groups explicitly, in contrast to administrator who is a member of Domain Administrators.

Try to save at this point. Glassfish seems to have a bug that will make this very difficult.When things go wrong, the page reappears with quotes around your arguments. Remove these and try again. The Base DB is particularly problematic. Initially put in a fake value FAKE. Save. Then if successful, change it to the real value. Save and pray. If nothing works, edit the appropriate Glassfish domain.xml file directly.

Add the following additional properties.

search-bind-dn [DOMAIN_NAME]\[searchUserName]
search-bind-password [searchUserPassword]
group-search-filter (&(objectClass=group)(member=%d))
search-filter (&(objectClass=user)(sAMAccountName=%s))
java.naming.referral follow

If you omit java.naming.referral=follow things will still work, but will result in unnessary errors being thrown in the server logs. Thanks to Przemyslaw Pelczar for this solution.

Screenshot from 2015-09-05 10:38:53

Save and restart Glassfish.

Linking

There is one more step to do. While the application server administrator has completed her task, it is up to the application deployer to relate application declared roles with AD groups and users.

Method One

Create a glassfish-web.xml file with

...
    <security-role-mapping>
        <role-name>ADUSER</role-name>
        <group-name>AuthenticatedUser</group-name>
    </security-role-mapping>
  
    <security-role-mapping>
        <role-name>ADADMIN</role-name>
        <group-name>Administrators</group-name>
    </security-role-mapping>
...

Note the use of the place-holder group AuthenticatedUser which matches all authenticated users.

Each security role mapping may have multiple group-name tags and multiple user-name tags, the latter for matching particular users.

The file needs to be placed into the WEB-INF directory of the war.

Screenshot from 2015-09-05 23:44:43

In this case, because of the AD nature of this application, this glassfish-web.xml file should be provided by the application developer. Generally, however, this mapping cannot be provided by the application developer, who knows only of realms and roles (see the second scenario).

Method Two

How can this be achieved on the server only, without messing with the war itself? We do not yet know.

The DNS Issue

While the above should work, it does not for us.

Glassfish is running on an Ubuntu 14.04 server from which we can successfully nslookup both [DOMAIN_NAME].[ROOT_NAME] and pdc.[DOMAIN_NAME].[ROOT_NAME], amongst others.

While valid authentication succeeds, in the server log we see

javax.naming.PartialResultException [Root exception is javax.naming.CommunicationException: [DOMAIN_NAME].[ROOT_NAME]:389 [Root exception is java.net.UnknownHostException: [DOMAIN_NAME].[ROOT_NAME]]]
...
Caused by: java.net.UnknownHostException: [DOMAIN_NAME].[ROOT_NAME]
	at java.net.AbstractPlainSocketImpl.connect(AbstractPlainSocketImpl.java:184)
	at java.net.SocksSocketImpl.connect(SocksSocketImpl.java:392)
	at java.net.Socket.connect(Socket.java:589)
	at java.net.Socket.connect(Socket.java:538)
	at java.net.Socket.(Socket.java:434)
	at java.net.Socket.(Socket.java:211)

The problem goes away if we add

[ipOfLDAPServer] [DOMAIN_NAME].[ROOT_NAME]

to our /etc/hosts file.


Udara Amarasinghe

Scenario Two

In scenario two, we are deploying a third party web application, a Contacts system, that has been declaratively locked down by the developers.

The delivered war looks something like this.

Screenshot from 2015-09-05 22:05:49

The provided web.xml contains:

    <security-role>
        <role-name>CONTACTS_USER</role-name>
    </security-role>

    <security-constraint>
        <display-name>Users of the Contacts System</display-name>
        <web-resource-collection>
            <web-resource-name>ContactsUserSystem</web-resource-name>
            <description />
            <url-pattern>/contacts-users/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description />
            <role-name>CONTACTS_USER</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <security-role>
        <role-name>CONTACTS_ADMIN</role-name>
    </security-role>

    <security-constraint>
        <display-name>Administrators of the Contacts System</display-name>
        <web-resource-collection>
            <web-resource-name>ContactsAdministrativeSystem</web-resource-name>
            <description />
            <url-pattern>/contacts-admins/*</url-pattern>
        </web-resource-collection>
        <auth-constraint>
            <description />
            <role-name>CONTACTS_ADMIN</role-name>
        </auth-constraint>
        <user-data-constraint>
            <transport-guarantee>NONE</transport-guarantee>
        </user-data-constraint>
    </security-constraint>

    <login-config>
        <auth-method>FORM</auth-method>
        <realm-name>ContactsRealm</realm-name>
        <form-login-config>
            <form-login-page>/login.xhtml</form-login-page>
            <form-error-page>/error.xhtml</form-error-page>
        </form-login-config>
    </login-config>


	

Create a realm with Name set to ContactsRealm and all other settings as in the first scenario.

Suppose that we only wish to allow special authenticated users access to the Contacts system. On the AD, create a group called Contacts Users and add the authorized users to this group.

Create a glassfish-web.xml file with

...
<security-role-mapping>
        <role-name>CONTACTS_USER</role-name>
        <group-name>Contacts Users</group-name>
    </security-role-mapping>
 
    <security-role-mapping>
        <role-name>CONTACTS_ADMIN</role-name>
        <group-name>Administrators</group-name>
    </security-role-mapping>
...

and insert into war.