Versions Compared

Key

  • This line was added.
  • This line was removed.
  • Formatting was changed.
Comment: Conform title and move to HOWTOs section.

...

Excerpt

This

document is a sample on how to resolve http://www.ja-sig.org/issues/browse/

tutorial describes how to configure CAS for LDAP DIGEST-MD5 authentication using OpenLDAP. It was authored in response to CAS-664.

Environment:

Server: Fedora 10 + CAS 3.3.1 + Tomcat 5.5.27 + OpenLDAP 2.4.12 + Cyrus SASL 2.1.22

Client: Fedora 10 + Firefox 3 (in the same computor with Server)

Realm: dell-d830

Config OpenLDAP DIGEST-MD5

1. Edit /etc/openldap/slapd.conf, add the following:

Code Block
password-hash {CLEARTEXT}
sasl-realm dell-d830
authz-regexp
    uid=(\[^HOWTO Configure CAS for LDAP DIGEST-MD5^,\]*),cn=dell-d830,cn=digest-md5,cn=auth
    ldap:///o=langhua,c=cn??sub?(uid=$1)

2. /etc/init.d/ldap restart

3. Add a user to OpenLDAP

uid=test,ou=beijing,o=langhua,c=cn

userPassword is 111111

Config SASL DIGEST-MD5

1. Add a user to SASL

Code Block
saslpasswd2 \-c test

Password: 111111

Again (for verification): 111111

2. Test SASL DIGEST-MD5

Open a console and run:

Code Block
sasl2-sample-server

Console output:

trying 2, 1, 6
trying 10, 1, 6
bind: Address already in use

...

Code Block
sasl2-sample-client \-m DIGEST-MD5 localhost

Console output:

receiving capability list... recv:
{53}
PLAIN CRAM-MD5 ANONYMOUS LOGIN NTLM GSSAPI DIGEST-MD5
PLAIN CRAM-MD5 ANONYMOUS LOGIN NTLM GSSAPI DIGEST-MD5
send:
{10}
DIGEST-MD5
send:
{1}
N
recv:
{114}
nonce="i7iU89Jmqj2S8BmExkXbkTwV8TaMdOrh1T803Q2UHw4=",realm="dell-d830",qop="auth",charset=utf-8,algorithm=md5-sess
please enter an authentication id:
please enter an authorization id: test
Password: 111111
send:
{231}
username="test",realm="dell-d830",nonce="i7iU89Jmqj2S8BmExkXbkTwV8TaMdOrh1T803Q2UHw4=",cnonce="h3prPJs4mCG0XAUJNiEhlISg0BjgS2UcRenpY6S3IuI=",nc=00000001,qop=auth,digest-uri="rcmd/localhost",response=e8f55dabc929152361c6cdcbb0d22532
recv:
{40}
rspauth=44285575e382f452e158665a2727a493
send:
{0}
successful authentication
closing connection

DIGEST-MD5 is ok.

Config CAS

1. Create /cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/BindLdapDigestMd5AuthenticationHandler.java

Code Block
titleBindLdapDigestMd5AuthenticationHandler.java
borderStylesolid
/*
 * Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license
 * distributed with this file and available online at
 * http://www.ja-sig.org/products/cas/overview/license/
 */
package org.jasig.cas.adaptors.ldap;

import org.jasig.cas.authentication.handler.AuthenticationException;
import org.jasig.cas.authentication.principal.UsernamePasswordCredentials;
import org.inspektr.common.ioc.annotation.IsIn;
import javax.naming.directory.DirContext;
import javax.naming.directory.SearchControls;

/**
 * Handler to do LDAP bind.
 *
 * @author Scott Battaglia
 * @version $Revision: 44152 $ $Date: 2008-08-13 15:18:57 -0400 (Wed, 13 Aug
 *          2008) $
 * @since 3.0.3
 */
public class BindLdapDigestMd5AuthenticationHandler extends
		AbstractLdapDigestMd5AuthenticationHandler {

	/** The default maximum number of results to return. */
	private static final int DEFAULT_MAX_NUMBER_OF_RESULTS = 1000;

	/** The default timeout. */
	private static final int DEFAULT_TIMEOUT = 1000;

	/** The search base to find the user under. */
	private String searchBase;

	/** The scope. */
	@IsIn( { SearchControls.OBJECT_SCOPE, SearchControls.ONELEVEL_SCOPE,
			SearchControls.SUBTREE_SCOPE })
	private int scope = SearchControls.SUBTREE_SCOPE;

	/** The maximum number of results to return. */
	private int maxNumberResults = DEFAULT_MAX_NUMBER_OF_RESULTS;

	/** The amount of time to wait. */
	private int timeout = DEFAULT_TIMEOUT;

	/** Boolean of whether multiple accounts are allowed. */
	private boolean allowMultipleAccounts;

	protected final boolean authenticateUsernamePasswordInternal(
			final UsernamePasswordCredentials credentials)
			throws AuthenticationException {

		DirContext context;

		context = this.getContextSource().getDirContext(
				credentials.getUsername(), credentials.getPassword());

		if (context == null) {
			return false;
		}

		return true;
	}

	protected String composeCompleteDnToCheck(final String dn,
			final UsernamePasswordCredentials credentials) {
		return dn;
	}

	private final SearchControls getSearchControls() {
		final SearchControls constraints = new SearchControls();
		constraints.setSearchScope(this.scope);
		constraints.setReturningAttributes(new String[0]);
		constraints.setTimeLimit(this.timeout);
		constraints.setCountLimit(this.maxNumberResults);

		return constraints;
	}

	/**
	 * Method to return whether multiple accounts are allowed.
	 *
	 * @return true if multiple accounts are allowed, false otherwise.
	 */
	protected boolean isAllowMultipleAccounts() {
		return this.allowMultipleAccounts;
	}

	/**
	 * Method to return the max number of results allowed.
	 *
	 * @return the maximum number of results.
	 */
	protected int getMaxNumberResults() {
		return this.maxNumberResults;
	}

	/**
	 * Method to return the scope.
	 *
	 * @return the scope
	 */
	protected int getScope() {
		return this.scope;
	}

	/**
	 * Method to return the search base.
	 *
	 * @return the search base.
	 */
	protected String getSearchBase() {
		return this.searchBase;
	}

	/**
	 * Method to return the timeout.
	 *
	 * @return the timeout.
	 */
	protected int getTimeout() {
		return this.timeout;
	}

	public final void setScope(final int scope) {
		this.scope = scope;
	}

	/**
	 * @param allowMultipleAccounts
	 *            The allowMultipleAccounts to set.
	 */
	public void setAllowMultipleAccounts(final boolean allowMultipleAccounts) {
		this.allowMultipleAccounts = allowMultipleAccounts;
	}

	/**
	 * @param maxNumberResults
	 *            The maxNumberResults to set.
	 */
	public final void setMaxNumberResults(final int maxNumberResults) {
		this.maxNumberResults = maxNumberResults;
	}

	/**
	 * @param searchBase
	 *            The searchBase to set.
	 */
	public final void setSearchBase(final String searchBase) {
		this.searchBase = searchBase;
	}

	/**
	 * @param timeout
	 *            The timeout to set.
	 */
	public final void setTimeout(final int timeout) {
		this.timeout = timeout;
	}
}

2. Create /cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/AbstractLdapDigestMd5AuthenticationHandler.java

Code Block
titleAbstractLdapDigestMd5AuthenticationHandler.java
/*
 * Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license
 * distributed with this file and available online at
 * http://www.ja-sig.org/products/cas/overview/license/
 */
package org.jasig.cas.adaptors.ldap;

import org.jasig.cas.adaptors.ldap.util.AuthenticatedLdapDigestMd5ContextSource;
import org.jasig.cas.authentication.handler.support.AbstractUsernamePasswordAuthenticationHandler;
import org.inspektr.common.ioc.annotation.NotNull;
import org.springframework.beans.factory.InitializingBean;
import org.springframework.ldap.core.LdapTemplate;
import org.springframework.util.Assert;

/**
 * Abstract class to handle common LDAP functionality.
 *
 * @author Scott Battaglia
 * @version $Revision: 42776 $ $Date: 2008-01-04 09:15:42 -0500 (Fri, 04 Jan 2008) $
 * @since 3.0.3
 */
public abstract class AbstractLdapDigestMd5AuthenticationHandler extends
    AbstractUsernamePasswordAuthenticationHandler implements InitializingBean {

    /** LdapTemplate to execute ldap queries. */
    @NotNull
    private LdapTemplate ldapTemplate;

    /** Instance of ContextSource */
    @NotNull
    private AuthenticatedLdapDigestMd5ContextSource contextSource;

    /** The filter path to the uid of the user. */
    @NotNull
    private String filter;

    /** Whether the LdapTemplate should ignore partial results. */
    private boolean ignorePartialResultException = false;

    /**
     * Method to set the datasource and generate a JdbcTemplate.
     *
     * @param contextSource the datasource to use.
     */
    public final void setContextSource(final AuthenticatedLdapDigestMd5ContextSource contextSource) {
        this.contextSource = contextSource;
        this.ldapTemplate = new LdapTemplate(contextSource);
    }

    public final void setIgnorePartialResultException(final boolean ignorePartialResultException) {
        this.ignorePartialResultException = ignorePartialResultException;
    }

    /**
     * Method to return the LdapTemplate
     *
     * @return a fully created LdapTemplate.
     */
    protected final LdapTemplate getLdapTemplate() {
        return this.ldapTemplate;
    }

    protected final AuthenticatedLdapDigestMd5ContextSource getContextSource() {
        return this.contextSource;
    }

    protected final String getFilter() {
        return this.filter;
    }

    public final void afterPropertiesSet() throws Exception {
        Assert.isTrue(this.filter.contains("%u"), "filter must contain %u");
        this.ldapTemplate.setIgnorePartialResultException(this.ignorePartialResultException);
    }

    /**
     * @param filter The filter to set.
     */
    public final void setFilter(final String filter) {
        this.filter = filter;
    }
}

3. Create /cas-server-support-ldap/src/main/java/org/jasig/cas/adaptors/ldap/util/AbstractLdapDigestMd5AuthenticationHandler.java

Code Block
titleAbstractLdapDigestMd5AuthenticationHandler.java
/*
 * Copyright 2007 The JA-SIG Collaborative. All rights reserved. See license
 * distributed with this file and available online at
 * http://www.ja-sig.org/products/cas/overview/license/
 */
package org.jasig.cas.adaptors.ldap.util;

import org.springframework.ldap.core.support.LdapContextSource;

import javax.naming.Context;
import javax.naming.directory.DirContext;
import javax.naming.directory.InitialDirContext;
import java.util.Enumeration;
import java.util.Hashtable;

public class AuthenticatedLdapDigestMd5ContextSource extends LdapContextSource {

	private DirContext context;

	public Hashtable<String, String> environment;

	public DirContext getDirContext(final String principal,
			final String credentials) {
		environment = (Hashtable) getAnonymousEnv().clone();

		String authenMethod = environment.get(Context.SECURITY_AUTHENTICATION);
		if (authenMethod == null) {
			environment.put(Context.SECURITY_AUTHENTICATION, "DIGEST-MD5");
		}
		String providerUrl = environment.get(Context.PROVIDER_URL);
		if (providerUrl == null) {
			environment.put(Context.PROVIDER_URL, assembleProviderUrlString(getUrls()));
		}

		environment.put(Context.SECURITY_PRINCIPAL, principal);
		environment.put(Context.SECURITY_CREDENTIALS, credentials);

                try {
			// Create the initial directory context
			context = new InitialDirContext(environment);
		} catch (Exception e) {
			System.err.println("Authentication failed: " + e);
			context = null;
		}
		return context;
	}

}

4. Build a new cas-server-support-ldap-3.3.1.jar and deploy it in CAS

5. Config deployerConfigContext.xml

Fill the new methods into the deployerConfigContext.xml.

Code Block
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE beans PUBLIC  "-//SPRING//DTD BEAN//EN" "http://www.springframework.org/dtd/spring-beans.dtd">
<!--
	| deployerConfigContext.xml centralizes into one file some of the declarative configuration that
	| all CAS deployers will need to modify.
	|
	| This file declares some of the Spring-managed JavaBeans that make up a CAS deployment.
	| The beans declared in this file are instantiated at context initialization time by the Spring
	| ContextLoaderListener declared in web.xml.  It finds this file because this
	| file is among those declared in the context parameter "contextConfigLocation".
	|
	| By far the most common change you will need to make in this file is to change the last bean
	| declaration to replace the default SimpleTestUsernamePasswordAuthenticationHandler with
	| one implementing your approach for authenticating usernames and passwords.
	+-->
<beans>
	<!--
		| This bean declares our AuthenticationManager.  The CentralAuthenticationService service bean
		| declared in applicationContext.xml picks up this AuthenticationManager by reference to its id,
		| "authenticationManager".  Most deployers will be able to use the default AuthenticationManager
		| implementation and so do not need to change the class of this bean.  We include the whole
		| AuthenticationManager here in the userConfigContext.xml so that you can see the things you will
		| need to change in context.
		+-->
	<bean id="authenticationManager"
		class="org.jasig.cas.authentication.AuthenticationManagerImpl">
		<!--
			| This is the List of CredentialToPrincipalResolvers that identify what Principal is trying to authenticate.
			| The AuthenticationManagerImpl considers them in order, finding a CredentialToPrincipalResolver which
			| supports the presented credentials.
			|
			| AuthenticationManagerImpl uses these resolvers for two purposes.  First, it uses them to identify the Principal
			| attempting to authenticate to CAS /login .  In the default configuration, it is the DefaultCredentialsToPrincipalResolver
			| that fills this role.  If you are using some other kind of credentials than UsernamePasswordCredentials, you will need to replace
			| DefaultCredentialsToPrincipalResolver with a CredentialsToPrincipalResolver that supports the credentials you are
			| using.
			|
			| Second, AuthenticationManagerImpl uses these resolvers to identify a service requesting a proxy granting ticket.
			| In the default configuration, it is the HttpBasedServiceCredentialsToPrincipalResolver that serves this purpose.
			| You will need to change this list if you are identifying services by something more or other than their callback URL.
			+-->
		<property name="credentialsToPrincipalResolvers">
			<list>
				<!--
					| UsernamePasswordCredentialsToPrincipalResolver supports the UsernamePasswordCredentials that we use for /login
					| by default and produces SimplePrincipal instances conveying the username from the credentials.
					|
					| If you've changed your LoginFormAction to use credentials other than UsernamePasswordCredentials then you will also
					| need to change this bean declaration (or add additional declarations) to declare a CredentialsToPrincipalResolver that supports the
					| Credentials you are using.
					+-->
				<bean
					class="org.jasig.cas.authentication.principal.UsernamePasswordCredentialsToPrincipalResolver" />
				<!--
					| HttpBasedServiceCredentialsToPrincipalResolver supports HttpBasedCredentials.  It supports the CAS 2.0 approach of
					| authenticating services by SSL callback, extracting the callback URL from the Credentials and representing it as a
					| SimpleService identified by that callback URL.
					|
					| If you are representing services by something more or other than an HTTPS URL whereat they are able to
					| receive a proxy callback, you will need to change this bean declaration (or add additional declarations).
					+-->
				<bean
					class="org.jasig.cas.authentication.principal.HttpBasedServiceCredentialsToPrincipalResolver" />
			</list>
		</property>

		<!--
			| Whereas CredentialsToPrincipalResolvers identify who it is some Credentials might authenticate,
			| AuthenticationHandlers actually authenticate credentials.  Here we declare the AuthenticationHandlers that
			| authenticate the Principals that the CredentialsToPrincipalResolvers identified.  CAS will try these handlers in turn
			| until it finds one that both supports the Credentials presented and succeeds in authenticating.
			+-->
		<property name="authenticationHandlers">
			<list>
				<!--
					| This is the authentication handler that authenticates services by means of callback via SSL, thereby validating
					| a server side SSL certificate.
					+-->
				<bean
					class="org.jasig.cas.authentication.handler.support.HttpBasedServiceCredentialsAuthenticationHandler" />

				<!--
					| This is the authentication handler declaration that every CAS deployer will need to change before deploying CAS
					| into production.  The default SimpleTestUsernamePasswordAuthenticationHandler authenticates UsernamePasswordCredentials
					| where the username equals the password.  You will need to replace this with an AuthenticationHandler that implements your
					| local authentication strategy.  You might accomplish this by coding a new such handler and declaring
					| edu.someschool.its.cas.MySpecialHandler here, or you might use one of the handlers provided in the adaptors modules.
					+-->
				<bean
					class="org.jasig.cas.adaptors.ldap.BindLdapDigestMd5AuthenticationHandler">
					<property name="filter" value="uid:caseExactmatch:=%u" />
					<property name="searchBase" value="o=langhua,c=cn" />
					<property
						name="contextSource"
						ref="contextSource" />
				</bean>
			</list>
		</property>
	</bean>

	<bean id="contextSource" class="org.jasig.cas.adaptors.ldap.util.AuthenticatedLdapDigestMd5ContextSource">
		<property name="anonymousReadOnly" value="true" />
		<property name="pooled" value="true" />
		<property name="urls">
			<list>
				<value>ldap://localhost:389/</value>
			</list>
		</property>
		<property name="baseEnvironmentProperties">
			<map>
        		</map>
		</property>
	</bean>

	<!--
	This bean defines the security roles for the Services Management application.  Simple deployments can use the in-memory version.
	More robust deployments will want to use another option, such as the Jdbc version.

	The name of this should remain "userDetailsService" in order for Acegi to find it.

	To use this, you should add an entry similar to the following between the two value tags:
	battags=notused,ROLE_ADMIN

	where battags is the username you want to grant access to.  You can put one entry per line.
	 -->
	<bean id="userDetailsService" class="org.springframework.security.userdetails.memory.InMemoryDaoImpl">
		<property name="userMap">
			<value>

		    </value>
		</property>
	</bean>

	<!--
	Bean that defines the attributes that a service may return.  This example uses the Stub/Mock version.  A real implementation
	may go against a database or LDAP server.  The id should remain "attributeRepository" though.
	 -->
	<bean id="attributeRepository"
		class="org.jasig.services.persondir.support.StubPersonAttributeDao">
		<property name="backingMap">
			<map>
				<entry key="uid" value="uid" />
				<entry key="eduPersonAffiliation" value="eduPersonAffiliation" />
				<entry key="groupMembership" value="groupMembership" />
			</map>
		</property>
	</bean>

	<!--
	Sample, in-memory data store for the ServiceRegistry. A real implementation
	would probably want to replace this with the JPA-backed ServiceRegistry DAO
	The name of this bean should remain "serviceRegistryDao".
	 -->
	<bean
		id="serviceRegistryDao"
		class="org.jasig.cas.services.InMemoryServiceRegistryDaoImpl" />
</beans>

6. Restart CAS


Login CAS

Visit http://localhost:8080/cas/login, enter test/111111 to login. This should be successful. Here is a piece of ldap log to show the ldap login procedure:

...