PHPGangsta - Der praktische PHP Blog

PHP Blog von PHPGangsta


Archive for the ‘Authentication’ tag

LDAP Authentifizierung und andere Abfragen

with 6 comments

Zuhause beim Programmieren oder bei der Erstellung von einfachen Webseiten wird man mit LDAP wahrscheinlich nicht viel zu tun haben, doch wenn man häufiger Intranet-Projekte erstellt oder in irgendeiner Weise den Login an ein OpenLDAP-Verzeichnis oder ActiveDirectory knüpfen will, kommt man an LDAP kaum vorbei.

Grundsätzliche Informationen über LDAP und ActiveDirectory findet man natürlich bei Wikipedia. Ich möchte hier nicht 5 Seiten darüber schreiben, wofür soetwas gut ist und warum man sowas haben sollte, da könnt ihr euch am besten selbst die Informationen sammeln. Stichworte sind wie gesagt LDAP, ActiveDirectory, OpenLDAP und für die hart gesottenen die RFC 2307.

Im Firmenumfeld werden Administrationsseiten (von denen es in größeren Firmen schnell Unmengen gibt) natürlich auch geschützt, sodass nur die berechtigten Personen darauf zugreifen können. Da man nicht für jede Seite ein eigenes Passwort etc. verwenden möchte, nutzt man das vorhandene ActiveDirectory, um Zugriffe auf die Seiten zu vergeben. Außerdem ist es damit einfacher, bei Ausscheiden eines Mitarbeiters zentral an einer Stelle den Account zu löschen.

Nur wie macht man das? Wie gestatte ich einem ActiveDirectory-User den Zugriff, wohingegen ich anderen den Zugriff verweigere?

Es gibt mehrere Lösungen. Zum einen bietet der Webserver da Möglichkeiten. Beim IIS zum Beispiel kann man bei der Konfiguration der Website entweder anonymen Zugriff erlauben (d.h. jeder Besucher kann sie betreten), oder man aktiviert eine Passwortabfrage, wie zB:

ldap1

Falls man dann die entsprechende Webseite betreten möchte, kommt eine Abfrage:

ldap2

Damit ist schonmal sichergestellt, dass man nur mit einen gültigen Account Zutritt erlangt. Doch wie bestimme ich nun, dass nur eine Untermenge aller Accounts zugelassen werden soll? Im Falle des IIS macht man das mit Hilfe der NTFS-Berechtigungen. Wenn man auf das DocumentRoot nur denjenigen Leserechte gibt, die auch die Seite betreten können sollen, haben wir genau das, was wir wollen.

Auch der Apache kann natürlich etwas vergleichbares. Dazu benötigt man das Modul mod_auth_ldap. Hier eine einfache Beispielkonfiguration:

<Location /example-repository>
    # LDAP soll für die Authentifizierung zuständig sein.
    AuthLDAPAuthoritative on

    AuthType Basic
    AuthName "Mein geschütztes Verzeichnis"

    # Wenn anonyme Zugriffe auf nicht erlaubt sind müssen sie hier
    # den DN für einen Benutzer angeben, der für den Lesezugriff
    # verwendet werden kann.
    AuthLDAPBindDN "CN=browse_user,OU=FunktionaleUser,DC=example,DC=com"
    # Das Passwort für den „Browse User“
    AuthLDAPBindPassword sicheres_passwort

    # Die LDAP URL für die Verbindung.
    # Alle Verzeichnisse unterhalb der angegebenen „Bind URL“ werden
    # durchsucht. Das Feld „login“ wird für die Suche nach dem Benutzernamen verwendet.
    # Format: scheme://host:port/basedn?attribute?scope?filter
    AuthLDAPURL "ldap://ldap.example.com:389/DC=example,DC=com?login?sub?(objectClass=*)"

    # Natürlich ist auch eine gesicherte Verbindung möglich. Beispiel:
    # "ldaps://ldap.example.com:636/DC=example,DC=com?login?sub?(objectClass=*)"

    Require valid-user
</Location>

Weitere Informationen dazu gibt es natürlich auf der entsprechenden Webseite zu mod_auth_ldap.

Dies sind also Methoden, die die Webserver allein regeln, PHP bekommt davon garnichts mit. Falls die Authentifizierung erfolgreich war (Der Login ist korrekt und dieser User hat Leserechte), kann man in PHP mittels der Variablen $_SERVER[‚AUTH_USER‘] den Benutzernamen herausfinden.

Um noch flexibler zu sein, kann man diese Authentifizierung natürlich auch direkt in PHP erledigen. Dazu bietet php einige ldap_* Funktionen, die es uns ermöglichen, via LDAP das Verzeichnis zu durchsuchen und Informationen daraus auszulesen. Außerdem kann man sich dann ein formschönes Login-Formular basteln, und hat nicht so einen grauen Kasten.

Um sich mit einem LDAP-Server verbinden zu können, benötigt man die Serverdaten, die Protokollversion und natürlich einen Login für den LDAP-Server, das ist der Bind-User. Das sieht dann ungefähr so aus:

function setupLdapConnection() {
	// get ldap connection to domainX
	$ldapOptions = array (
				'binddn'    => 'cn=ldapsearch,ou=serviceuser,DC=domainX,DC=net',
				'bindpw'    => 'ldapsearchpwd',
				'basedn'    => 'DC=domainX,DC=net',
				'host'      => 'ldap.domainX.net'    );
	
	$ldap = ldap_connect($ldapOptions['host']);
	if ($ldap == false) {
		throw new Exception('LDAP connnect failed');
	}
	
	ldap_set_option($ldap, LDAP_OPT_PROTOCOL_VERSION, 3);
	ldap_set_option($ldap, LDAP_OPT_REFERRALS, 0);
	
	$res = ldap_bind($ldap, $ldapOptions['binddn'], $ldapOptions['bindpw']);
	if ($res === false) {
		throw new Exception('Unable to bind to LDAP server');
	}
	
	return $ldap;
}

Damit haben wir nun eine LDAP-Connection aufgebaut, und dann kann man anfangen zu Suchen, beispielsweise so:

$ldap = setupLdapConnection();

$baseDn = 'DC=domainX,DC=net';
$attributes = array('cn', 'mail', 'objectClass', 'sAMAccountName', 'extensionAttribute13');
$filter = 'cn=User123';

$res = ldap_search($ldap, $baseDn, $filter, $attributes);

if ($res === false) {
	return "LDAP search failed\r\n";
}

if (ldap_count_entries($ldap, $res) === 0) {
	return "LDAP search failed, no entries found\r\n";
}

$entry = ldap_get_entries($ldap, $res);
if ($entry === false) {
	return "LDAP get entry failed\r\n";
}

return $res;

In $res haben wir dann das Ergebnis-Array mit den gewünschten Informationen. Man kann natürlich auch mit Hilfe der $baseDn nur in einem bestimmten Zweig suchen, oder mit Hilfe des $filter nach anderen Attributen suchen.

Mit diesen Funktionen kann man dann auch zB prüfen, ob ein User Mitglied einer Gruppe ist (Attribut memberOf), oder seine Email-Adresse herausfinden oder oder…

Zur Authentifizierung selbst gibt es natürlich auch schon fertige Klassen. Zu nennen sind da wohl PEAR_Auth und Zend_Auth_Adapter_Ldap. Hier ein schönes kurzes Beispiel mit dem ZF:

application.ini:

ldap.server1.host = ldap.domainX.net
ldap.server1.useSsl = false

ldap.server1.accountDomainName = domainX.net
ldap.server1.accountDomainNameShort = domainX
ldap.server1.accountCanonicalForm = 3
ldap.server1.accountFilterFormat = "(&(objectClass=user)(sAMAccountName=%s))"

ldap.server1.username = "cn=ldapsearch,ou=serviceuser,DC=domainX,DC=net"
ldap.server1.password = ldapsearchpwd
ldap.server1.baseDn = "DC=domainX,DC=net"
ldap.server1.bindRequiresDn = true

IndexController:

public function loginAction() {
	$form = new forms_LoginForm();

    if ($this->getRequest()->isPost()) {
    	$formData = $this->getRequest()->getPost();
        if ($form->isValid($formData)) {
			$values = $form->getValues();
				
			$loginSuccessful = false;
			$auth = Zend_Auth::getInstance();
			
			$options = Zend_Registry::get('configIni')->ldap->toArray();
			
			$adapter = new Zend_Auth_Adapter_Ldap($options, 
												$values['loginusername'], 
												$values['loginpassword']);
			$result = $auth->authenticate($adapter);

			if ($result->isValid()) {					
				$namespace = new Zend_Session_Namespace();
				$namespace->username = $values['loginusername'];				
			} else {
				$this->_flashMessenger->addMessage('error:login_failed_ldap');					
			}
			$this->_redirect("");				
		} else {
			$form->populate($formData);
		}
               
    }
    $this->view->form = $form;
}

Ist natürlich alles auf das Wesentliche gekürzt, man hat normalerweise natürlich noch diverse Sicherheitsabfragen oder zB einen try-catch-Block um das authenticate() etc.

Nutzt ihr auch LDAP, und wenn ja wie?

Written by Michael Kliewe

August 7th, 2009 at 4:06 pm