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.
- 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
- 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.
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
in which case the base DN is
Case is unimportant. (For example |
[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
.
Click on New
to create a new realm.
Set
Name | ActiveDirectoryRealm |
See web.xml . |
Class Name | com.sun.enterprise.security.auth.realm.ldap.LDAPRealm |
Sun LDAPRealm. |
Glassfish will then present more options.
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.
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.
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.
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.
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.
Recent Comments