Transparent LDAP-based Remote Address Authentication Handler
Transparent LDAP Remote Address Authentication Handler
Introduction
Directory servers such as eDirectory store an authenticated user's network address. In the case of eDirectory this network address is recorded when a user logs into a desktop using the Novell Client. This address is then exposed via the networkAddress LDAP attribute. This stored information can be used to transparently authenticate a user to CAS by performing an LDAP lookup for the client address and relevant user credentials. For example:
- A user logs into their desktop computer's Novell Client.
- On successful log-in they browse to a CAS-enabled web application.
- The web application redirects to CAS.
- CAS checks with eDirectory to see if the client address has an associated user account. If success the relevant user credentials are returned.
- CAS seamlessly authenticates the user with the web application based on the returned credentials.
The mechanics of this approach are very similar to X.509 certificate authentication, but trust is instead placed on the client network address stored in LDAP rather than a certificate.
The benefit of this approach is that transparent authentication is achieved within a large corporate network without the need to manage certificates. The drawback is that an LDAP-cached network address is not very trustworthy. With this in mind this authentication mechanism should only be enabled for internal network clients with relatively static I.P. addresses.
Caveats
- This handler assumes internal clients will be hitting the CAS server directly and not coming via a web proxy. In the event of the client using the web proxy the likelihood of the remote address lookup succeeding is reduced because to CAS the client address is that of the proxy server and not the client. Given that this form of CAS authentication would typically be deployed within an internal network this is generally not a problem.
- Currently this handler only supports eDirectory 8.7 or earlier. Novell has changed the way networkAddress values are stored within their directory and a patch is required to this handler to enable support.
- At the time of writing it seems only eDirectory records the network address of authenticated clients. Even in this case only clients who have authenticated using the Novell Client and have active NCP shares seem to have their I.P. addresses recorded. As a consequence if you use Active Directory or OpenLDAP this transparent authentication handler will probably be of little benefit to you.
Before you start
The necessary functionality is included in the cas-server-support-ldap module. Generally you would configure CAS to authenticate to LDAP prior to configuring LDAP remote address authentication. This way if retrieving user principals from the remote address is unsuccessful the user can manually log into CAS using their LDAP (i.e. eDirectory) credentials.
Including the Handler
In the pom.xml file for your CAS webapp (the default is ${project.home}/cas-server-webapp/pom.xml) add the following dependency:
<dependency> <groupId>${project.groupId}</groupId> <artifactId>cas-server-support-ldap</artifactId> <version>${project.version}</version> </dependency>
Set up an LDAP context source
Using the instructions in the LDAP authentication page configure an LDAP context source for CAS to use. For example:
<bean id="contextSource" class="org.jasig.cas.adaptors.ldap.util.AuthenticatedLdapContextSource"> <property name="pooled" value="true"/> <property name="urls"> <list> <value>ldaps://ldap.server.com/</value> </list> </property> <property name="userName" value="{bind_username_goes_here}"/> <property name="password" value="{bind_user_password_goes_here}"/> <property name="baseEnvironmentProperties"> <map> <entry> <key> <value>java.naming.security.authentication</value> </key> <value>simple</value> </entry> </map> </property> </bean>
Note: You may or may not have all these properties set. This is just an example.
Configuring Web Flow
The original webflow XML configuration file is located in ${project.home}/cas-server-webapp/src/main/webapp/WEB-INF/login-webflow.xml.
Webflow consists of a sequence of operations. Some operations are tests that yield a true or false result. Other operations yield named results like "success" or "error". Each result causes the flow to move to another operation.
The operation that displays the Form with the userid and password fields is the "viewLoginForm". There are two ways to get to viewLoginForm. You can fall through to it from the sequence of preliminary tests (to see if you already have a login cookie, etc.), or you can come back to it if you enter a bad userid or password. The configuration will be changed to add LDAP Remote Address handling to the sequence of preliminary tests.
Note: SPNEGO and X.509 are other optional tests. If you are planning on supporting multiple automatic authentication mechanisms please read the associated documentation and configure your web-flow accordingly.
First create a new action-state called "startAuthenticate".
<action-state id="startAuthenticate"> <action bean="remoteAddressCheck" /> <transition on="success" to="sendTicketGrantingTicket" /> <transition on="error" to="viewLoginForm" /> </action-state>
Although the name of the state is "startAuthenticate", you can see from the source that this is a "remoteAddressCheck". SPNEGO and X.509 also uses the name "startAuthenticate" for its test, so if you use SPNEGO, X509 or Remote Address, then one of the the three will be "startAuthenticate" and the others will be something else, like "continueAuthenticate1" & "continueAuthenticate2". In that case, the transition on="error" of the first (startAuthenticate) will be changed from "viewLoginForm" to the id of the second, "continueAuthenticate1".
Now change the outcome of the previous test. It now reads
<if test="${externalContext.requestParameterMap['renew'] != '' && externalContext.requestParameterMap['renew'] != null}" then="viewLoginForm" else="generateServiceTicket" />
Change the outcome of the test from "viewLoginForm" to "startAuthenticate", so that when the test is true it jumps to the startAuthenticate test you just defined. Now go back a few tests to the gatewayRequestCheck and do almost the same thing:
<if test="${externalContext.requestParameterMap['gateway'] != '' && externalContext.requestParameterMap['gateway'] != null && flowScope.service != null}" then="redirect" else="viewLoginForm" />
The one difference is that in this test, the "viewLoginForm" that you change to "startAuthenticate" is in the else part of the test.
Now that the WebFlow itself has been configured, the "remoteAddressCheck" bean mentioned above has to be defined. That is done in the /WEB-INF/cas-servlet.xml file. Add the following bean definition to this file:
<bean id="remoteAddressCheck" class="org.jasig.cas.adaptors.ldap.remote.RemoteAddressNonInteractiveCredentialsAction"> <property name="centralAuthenticationService" ref="centralAuthenticationService"/> </bean>
This bean extracts the client's I.P. address from the request so that it can be checked against LDAP prior to the display of the login form.
Configuring the Authentication Handler
This authentication handler checks the inbound client I.P. address to see if it falls within the internal network. The RemoteAddressAuthenticationHandler bean has one property:
- ipNetworkRange - This defines the internal network parameters in the form of a subnet and netmask. e.g. 192.168.1.0/255.255.255.0 or 10.0.0.0/255.255.0.0
For security reasons only clients within the internal network should be granted transparent authentication via the LDAP Remote Address handler.
One bean must be added to the list of Authentication Handlers. In file WEB-INF/deployerConfigContext find the <bean id="authenticationManager">. Inside it, there is a <property name="authenticationHandlers">. In it is a <list>. To this list, add:
<!-- | The RemoteAddressAuthenticationHandler ensures the client I.P. address falls within that of the internal network | Used to ensure that only internal clients are transparently logged into CAS by the LDAP Remote Address principal resolver. +--> <bean class="org.jasig.cas.adaptors.ldap.remote.RemoteAddressAuthenticationHandler"> <property name="ipNetworkRange" value="{network_range_goes_here}"/> </bean>
Configuring the Credentials to Principal Resolver
This principal resolver performs the LDAP lookup for the I.P. address returned by the authentication handler.
The RemoteIpLookupCredentialsToPrincipalResolver bean has the following properties:
- filter - The LDAP search filter to apply when performing a network address search. e.g. (&(networkAddress=%u)(objectClass=inetOrgPerson))
Note: In an XML file the & LDAP search character needs to be replaced with & to avoid parse errors. - principalAttributeName - The LDAP attribute to return as the Principal name. e.g. cn
- ipAddressFormat - The format by which the network address is stored in the directory.
Currently this handler supports two formats, standard (i.e. 192.168.1.1) and edirectory87 (a byte array). In the future it is hoped more support will be added. - searchBase - The LDAP search base to use when performing a search. e.g. ou=users,o=organisaiton
- contextSource - A reference to the previously defined LDAP context source bean.
One bean must be added to the list of Credential to Principal Resolvers in the WEB-INF/deployerConfigContext file. In the "authenticationManger" find the property name="credentialsToPrincipalResolvers" which has a <list>. To this list, add:
<bean class="org.jasig.cas.adaptors.ldap.remote.RemoteIpLookupCredentialsToPrincipalResolver"> <!-- | The 'filter' attribute defines the LDAP search filter to be used. Like other CAS LDAP filters %u | is a placeholder for the user credential. In the case of trusted LDAP is the I.P. address of the client. | e.g. (&(networkAddress=%u)(objectClass=inetOrgPerson)) +--> <property name="filter" value="{search_filter_goes_here}" /> <!-- | The 'principalAttributeName' identifies the LDAP attribute that will be returned as the CAS principal value. | e.g. cn +--> <property name="principalAttributeName" value="{principal_attribute_goes_here}" /> <!-- | The 'ipAddressFormat' attribute identifies the format in which the LDAP source stores the I.P. address of the client. | Currently this property can be set to either 'standard' or 'edirectory87'. | | The standard format will perform a search based on the default I.P. address format returned by request.getRemoteAddr(). | | eDirectory stores I.P. addresses in a hashed byte array hence they must be converted prior to performing an LDAP search. | If performing trusted LDAP lookups against eDirectory use the 'edirectory87' setting. | | NOTE: eDirectory 8.8+ uses a different format for storing I.P. addresses and is currently not supported. +--> <property name="ipAddressFormat" value="{stored_ip_format_goes_here}" /> <!-- | The 'searchBase' attribute identifies the LDAP search base in which to search within. | e.g. ou=users,o=organisation +--> <property name="searchBase" value="{search_base_goes_here}" /> <!-- | The 'contextSource' attribute identifies an LDAPContextSource bean to use to create a connection to the LDAP server. | This parameter is exactly the same as other contextSource attributes used in similar LDAP related CAS configurations. +--> <property name="contextSource" ref="contextSource" /> </bean>