MSDN Magazin > Home > Ausgaben > 2008 > January >  Zum Nachschlagen: Verwalten von Verzeichnissich...
Zum Nachschlagen
Verwalten von Verzeichnissicherheitsprinzipalen in .NET Framework 3.5
Joe Kaplan and Ethan Wilansky
Codedownload verfügbar unter: DSAccountManagement2008_01.exe (160 KB)
Browse the Code Online

Dieser Artikel basiert auf einer Vorabversion von Visual Studio 2008. Änderungen an allen Informationen in diesem Artikel sind vorbehalten.
Themen in diesem Artikel:
  • Die System.DirectoryServices.AccountManagement-Klasse
  • Active Directory-Domänendienste
  • Active Directory Lightweight Directory Services (AD LDS)
  • Verwalten von Benutzern, Computern und Gruppenprinzipalen
In diesem Artikel werden folgende Technologien verwendet:
.NET Framework 3.5, Visual Studio 2008
Verzeichnisse stellen eine wichtige wenn auch selten gemeisterte Komponente in der Entwicklung von Unternehmensanwendungen dar. Für die Windows®-Plattform stellt Microsoft drei primäre Verzeichnisplattformen bereit: Active Directory®-Domänendienste, den lokalen SAM-Datenspeicher (Security Account Manager) auf jedem Windows-Computer und die verhältnismäßig neuen Active Directory Lightweight Directory Services (AD LDS), die Ihnen möglicherweise unter dem früheren Namen „Active Directory-Anwendungsmodus“ bzw. ADAM bekannt waren. Die meisten Unternehmensentwickler sind zumindest mit den Grundlagen der SQL-Programmierung vertraut, doch es gibt nur wenige, die in der Programmierung von Verzeichnisdiensten Erfahrung haben.
Die ursprüngliche Version von Microsoft® .NET Framework stellte einen Satz von Klassen für das Programmieren von Verzeichnisdiensten innerhalb des System.DirectoryServices-Namespace bereit. Bei diesen Klassen handelte es sich um eine einfache verwaltete Interop-Ebene über einer vorhandenen COM-basierten Betriebssystemkomponente (insbesondere Active Directory Service Interfaces bzw. ADSI). Das Programmiermodell war zwar relativ leistungsfähig, im Unterschied zum ADSI-Modell jedoch allgemeiner und schlanker.
In .NET Framework 2.0 hat Microsoft den System.DirectoryServices Features hinzugefügt und zwei neue Namespaces bereitgestellt: „System.DirectoryServices.ActiveDirectory“ und „System.DirectoryServices.Protocols“. (Diese werden im Folgenden als der ActiveDirectory-Namespace bzw. der Protocols-Namespace bezeichnet.) Mit dem ActiveDirectory-Namespace wurden umfangreiche neue Klassen für die stark typisierte Verwaltung von Komponenten auf der Infrastrukturebene eingeführt, zum Beispiel Server, Domänen, Gesamtstrukturen, Schemas und Replikationen. Der Protocols-Namespace entwickelte sich in eine andere Richtung und stellte eine alternative API zum Programmieren von LDAP (Lightweight Directory Access-Protokoll) bereit. Dabei wurde direkt mit dem Windows-LDAP-Subsystem (wldap32.dll) gearbeitet und die ADSI COM-Interop-Ebene völlig unberücksichtigt gelassen (siehe Abbildung 1).
Abbildung 1 Microsoft-Verzeichnisdienste – Programmierarchitektur (Klicken Sie zum Vergrößern auf das Bild)
Trotzdem wurden von Entwicklern einige der stark typisierten Schnittstellen in ADSI vermisst, die zuvor beim Verwalten der Sicherheitsprinzipale wie Benutzer und Gruppen verwendet wurden. Die meisten dieser Aufgaben konnten mithilfe der allgemeineren Klassen in System.DirectoryServices durchgeführt werden, doch ihre Verwendung war nicht so einfach wie erwartet, und viele Aufgaben waren sogar ziemlich unklar. Microsoft hat dieses Problem in .NET Framework 3.5 durch Hinzufügen eines neuen Namespace behandelt, der speziell für das Verwalten von Sicherheitsprinzipalen entworfen wurde: System.DirectoryServices.AccountManagement. (Im Folgenden wird System.DirectoryServices.AccountManagement als der AccountManagement-Namespace bezeichnet.)
Dieser neue Namespace hat drei Hauptziele: das Vereinfachen der Hauptverwaltungsvorgänge auf allen drei Verzeichnisplattformen, das konsistente Gestalten der Hauptverwaltungsvorgänge unabhängig vom zugrunde liegenden Verzeichnis und das Bereitstellen zuverlässiger Ergebnisse für diese Vorgänge, sodass Sie Vorbehalte und Sonderfälle nicht bis ins kleinste Detail kennen müssen.
Microsoft hat sich ein paar Jahre Zeit gelassen, bis die .NET-Landschaft Gestalt annahm, und konnte daher die bisherige Arbeit in ADSI sogar übertreffen. Es wurde eine verbesserte API für diese Features bereitgestellt, die sowohl die .NET-Funktionen nutzt als auch eine viel bessere Unterstützung für neue Verzeichnisplattformen wie AD LDS bietet.

Verzeichnisdienste-Programmierarchitektur
Werfen Sie vor dem Weiterlesen einen Blick auf den Codedownload für diesen Artikel, und überprüfen Sie die Voraussetzungen, die Sie zur Verwendung dieser Verfahren bereits erfüllt haben sollten. Jetzt kann es losgehen. In Abbildung 1 wird die gesamte Programmierarchitektur von System.DirectoryServices dargestellt. Der AccountManagement-Namespace ist wie der ActiveDirectory-Namespace eine Abstraktionsebene über System.DirectoryServices, und System.DirectoryServices selbst ist eine Abstraktionsebene über ADSI. Der AccountManagement-Namespace hängt außerdem im Hinblick auf eine kleine Gruppe seiner Features, zum Beispiel die Hochleistungsauthentifizierung, vom Protocols-Namespace ab. Die dunkelblaue Schattierung in Abbildung 1 zeigt die Teile der Verzeichnisdienste-Programmierarchitektur an, auf denen „AccountManagement“ beruht.
In Abbildung 2 sind die wichtigsten Typen im AccountManagement-Namespace dargestellt. Im Unterschied zu den in .NET Framework 2.0 hinzugefügten Namespaces hat „AccountManagement“ eine verhältnismäßig kleine Oberfläche. Mit Ausnahme einiger unterstützender Typen wie Enumerations- und Ausnahmeklassen besteht der Namespace grundsätzlich aus drei Teilen: einer Struktur von prinzipalabgeleiteten Klassen, die stark typisierte Benutzer-, Gruppen- und Computerobjekte darstellen, einer PrincipalContext-Klasse, mit der eine Verbindung zum zugrunde liegenden Speicher eingerichtet wird und einer PrincipalSearcher-Klasse (mit unterstützenden Typen) zur Suche von Objekten im Verzeichnis.
Abbildung 2 Wichtige Klassen in System.DirectoryServices.AccountManagement (Klicken Sie zum Vergrößern auf das Bild)
Intern verwendet der AccountManagement-Namespace ein Anbieterentwurfsmuster für den Vorgang in allen drei unterstützten Verzeichnisplattformen. Demzufolge verhalten sich die Mitglieder der verschiedenen Prinzipalklassen ähnlich, unabhängig vom zugrunde liegenden Verzeichnisspeicher. Dieser Entwurf ist der Schlüssel zum Bereitstellen von Einfachheit und Konsistenz.

Einrichten eines Kontexts
Sie verwenden die PrincipalContext-Klasse, um eine Verbindung zum Zielverzeichnis einzurichten und die Anmeldeinformationen für das Durchführen von Vorgängen im Verzeichnis anzugeben. Dieser Ansatz ist so ähnlich wie etwa das Einrichten eines Kontexts mit der DirectoryContext-Klasse im ActiveDirectory-Namespace.
Der PrincipalContext-Konstruktor umfasst viele verschiedene Überladungen zum Bereitstellen der genauen Optionen, die Sie für das Einrichten eines Kontexts benötigen. Wenn Sie bisher mit der DirectoryEntry-Klasse in System.DirectoryServices gearbeitet haben, werden Sie mit den meisten PrincipalContext-Optionen vertraut sein. Die drei PrincipalContext-Optionen „ContextType“, „Name“ und „Container“ sind jedoch viel spezifischer als die Eingabeparameter, die Sie für die DirectoryEntry-Klasse verwenden. Dieser Genauigkeitsgrad gewährleistet, dass Sie die richtigen Eingabeparameterkombinationen verwenden. In System.DirectoryServices und ADSI werden diese drei Parameter des PrincipalContext-Konstruktors in einer einzelnen Zeichenfolge kombiniert, die der Pfad genannt wird. Werden diese Komponenten getrennt, ist es leichter zu verstehen, welchen Zweck die verschiedenen Teile des Pfads haben.
Sie verwenden die ContextType-Enumeration, um die Art des Zielverzeichnisses anzugeben: Domäne (für Active Directory-Domänendienste), ApplicationDirectory (für AD LDS) oder Computer (für die lokale SAM-Datenbank). Wenn Sie dagegen System.DirectoryServices verwenden, geben Sie den Zielspeicher mit der Anbieterkomponente der Pfadzeichenfolge (in der Regel „LDAP“ oder „WinNT“) an. ADSI liest dann diesen Wert intern, um den passenden Anbieter zu laden.
Die Klassen im AccountManagement-Namespace erleichtern diese Aufgabe und gewährleisten mehr Konsistenz, indem sie sicherstellen, dass Sie nur die vom Framework unterstützten Anbieter verwenden. ADSI vermeidet außerdem lästige falsche Schreibweisen und eine inkorrekte Groß- und Kleinschreibung in der ADSI- und System.DirectoryServices-Programmierung. Andererseits unterstützt „AccountManagement“ weniger gängige ADSI-Anbieter wie IIS- und Novell-Verzeichnisdienste nicht.
Sie verwenden den Name-Parameter für den PrincipalContext-Konstruktor, um den Namen des jeweiligen Verzeichnisses bereitzustellen, mit dem eine Verbindung hergestellt werden soll. Dies kann der Name eines bestimmten Servers, Computers oder einer bestimmten Domäne sein. Dabei muss Folgendes beachtet werden: Wenn dieser Parameter null ist, versucht „AccountManagement“, aufgrund Ihres aktuellen Sicherheitskontexts einen Standardcomputer oder eine Standarddomäne für die Verbindung festzulegen. Wenn Sie jedoch eine Verbindung zu einem AD LDS-Speicher herstellen wollen, müssen Sie einen Wert für den Name-Parameter angeben.
Der Container-Parameter ermöglicht die Angabe des Zielorts im Verzeichnis zum Einrichten eines Kontexts. Sie dürfen diesen Parameter nicht angeben, wenn Sie „Machine ContextType“ verwenden, da die SAM-Datenbank nicht hierarchisch ist. Umgekehrt müssen Sie einen Wert bereitstellen, wenn Sie „ApplicationDirectory“ verwenden, da AD LDS kein defaultNamingContext-Attribut veröffentlicht, das beim Ableiten des Verzeichnisstammobjekts verwendet werden kann. Dieser Parameter ist bei „Domain ContextType“ optional, und wenn er nicht angegeben ist, verwendet „AccountManagement“ den „defaultNamingContext“.
Mit den zusätzlichen Parametern (Benutzername, Kennwort und die ContextOptions-Enumeration) können Sie falls notwendig die Nur-Text-Anmeldeinformationen bereitstellen und die verschiedenen zu verwendenden Verbindungssicherheitsoptionen angeben.
Alle Verzeichnisse unterstützen die Windows-Authentifizierungsmethode „Aushandeln“. Wenn für den Computerspeicher keine Option angegeben ist, wird die Windows-Aushandlungsauthentifizierung verwendet. Die Standardeinstellung für „Domain“ und „ApplicationDirectory“ ist jedoch die Windows-Aushandlung einschließlich Signatur und Versiegelung.
Beachten Sie, dass Active Directory-Domänendienste und AD LDS auch die einfache Bindung in LDAP unterstützen. In der Regel sollte diese nicht mit den Active Directory-Domänendiensten gemeinsam eingesetzt werden, kann jedoch mit AD LDS erforderlich sein, wenn Sie möchten, dass ein Benutzer im AD LDS-Speicher Prinzipalvorgänge durchführt.
Wenn Sie für die Benutzernamen- oder Kennwortparameter null angeben, verwendet „AccountManagement“ den aktuellen Windows-Sicherheitskontext. Wenn Sie Anmeldeinformationen angeben, werden die folgenden Formate für den Benutzernamen unterstützt: „SamAccountName“, „UserPrincipalName“ und „NT4Name“. In Abbildung 3 sind drei Möglichkeiten zum Einrichten eines Kontexts dargestellt.
// create a context for a domain called Fabrikam pointed
// to the TechWriters OU and using default credentials
PrincipalContext domainContext = new PrincipalContext( 
  ContextType.Domain,"Fabrikam","ou=TechWriters,dc=fabrikam,dc=com");

// create a context for the current machine SAM store with the 
// current security context
PrincipalContext machineContext = new PrincipalContext(
  ContextType.Machine);

// create a context for an AD LDS store pointing to the 
// partition root using the credentials for a user in the AD LDS store 
// and SSL for encryption
PrincipalContext ldsContext = new PrincipalContext(
    ContextType.ApplicationDirectory, "sea-dc-02.fabrikam.com:50001", 
    "ou=ADAM Users,o=microsoft,c=us", 
    ContextOptions.SecureSocketLayer | ContextOptions.SimpleBind, 
    "CN=administrator,OU=ADAM Users,O=Microsoft,C=US ", "pass@1w0rd01");

Beim Bindungsvorgang gibt es eine weitere feine aber wichtige Unterscheidung zwischen den Klassen „AccountManagement PrincipalContext“ und „System.DirectoryServices DirectoryEntry“. „PrincipalContext“ stellt eine Verbindung her und bindet an das zugrunde liegende Verzeichnis, sobald ein Objekt erstellt wird, während „DirectoryEntry“ erst dann bindet, wenn Sie einen anderen Vorgang durchführen, der die Verbindung erzwingt. Dementsprechend erhalten Sie mit „PrincipalContext“ ein unmittelbares Feedback dazu, ob eine Verbindung erfolgreich an ein Verzeichnis gebunden wurde.

Erstellen von Benutzerkonten
Im ersten Teil wurde ausführlich beschrieben, wie „AccountManagement“ „PrincipalContext“ verwendet, um eine Verbindung herzustellen und an einen Container zu binden. Im Folgenden wird ein typischer DirectoryServices-Vorgang erläutert: das Erstellen eines Benutzerkontos. Dabei weist jedes Codebeispiel einem obligatorischen Attribut einen Wert zu, fügt zwei optionale Attribute hinzu, legt ein Kennwort fest, aktiviert das Benutzerkonto und leitet die Änderungen an das Verzeichnis weiter.
Hier wird die domainContext-Variable verwendet, die in Beispiel 1 in Abbildung 3 eingeführt wurde, um einen neuen „UserPrincipal“ zu erstellen:
// create a user principal object
UserPrincipal user = new UserPrincipal(domainContext,
     "User1Acct", "pass@1w0rd01", true);

// assign some properties to the user principal
user.GivenName = "User";
user.Surname = "One";

// force the user to change password at next logon
user.ExpirePasswordNow();

// save the user to the directory
user.Save();
Der „domainContext“ stellt die Verbindung zum Verzeichnis und dem Sicherheitskontext her, die zum Ausführen des Vorgangs erforderlich sind. Anschließend wird in einer einzelnen Codezeile ein neues Benutzerobjekt erstellt, das Kennwort festgelegt und aktiviert. Danach werden mithilfe der GivenName- und Surname-Eigenschaften die entsprechenden Verzeichnisattribute im zugrunde liegenden Speicher festgelegt. Vor dem Speichern des Objekts im zugrunde liegenden Verzeichnisspeicher läuft das Kennwort ab. Dadurch wird der Benutzer gezwungen, das Kennwort bei der ersten Anmeldung zu ändern.
Im Vergleich dazu werden in Abbildung 4 die gleichwertigen Schritte vorgeführt, die zum Erstellen eines Benutzerkontos in System.DirectoryServices erforderlich sind. Bei der Containervariablen in der ersten Codezeile handelt es sich um ein DirectoryEntry-Klassenobjekt, das einen Pfad für seine Verbindung verwendet. Der Pfad gibt den Anbieter, eine Domäne und einen Container (TechWriters-Organisationseinheit) an. Er stellt außerdem mithilfe des Sicherheitskontexts des aktuellen Benutzers eine Verbindung her. Die Containervariable verhält sich ähnlich wie „domainContext“ im vorherigen Beispiel für das Erstellen eines Benutzerprinzipals.
    DirectoryEntry container =
    new DirectoryEntry("LDAP://ou=TechWriters,dc=fabrikam,dc=com");
// create a user directory entry in the container
DirectoryEntry newUser = container.Children.Add("cn=user1Acct", "user");

// add the samAccountName mandatory attribute
newUser.Properties["sAMAccountName"].Value = "User1Acct";

// add any optional attributes
newUser.Properties["givenName"].Value = "User";
newUser.Properties["sn"].Value = "One";

// save to the directory
newUser.CommitChanges();

// set a password for the user account
// using Invoke method and IadsUser.SetPassword
newUser.Invoke("SetPassword", new object[] { "pAssw0rdO1" });

// require that the password must be changed on next logon
newUser.Properties["pwdLastSet"].Value = 0;

// enable the user account
// newUser.InvokeSet("AccountDisabled", new object[]{false});
// or use ADS_UF_NORMAL_ACCOUNT (512) to effectively unset the
// disabled bit
newUser.Properties["userAccountControl"].Value = 512;

// save to the directory
newUser.CommitChanges();
   
Abgesehen von der Tatsache, dass dieser Code deutlich länger ist als das AccountManagement-Beispiel, ist es auch komplexer. Sie müssen dazu mehr über das zugrunde liegende Datenmodell im Verzeichnis wissen, und Sie müssen wissen, wie bestimmte Kontoverwaltungsfeatures, zum Beispiel das Festlegen eines anfänglichen Kennworts und das Aktivieren eines Benutzerobjekts durch Rückgängigmachen des deaktivierten Kennzeichens, zu behandeln sind. Dies wird etwas unhandlich, wenn Sie die auf Spiegelung basierten Invoke- und InvokeSet-Methoden verwenden müssen, um die zugrunde liegenden ADSI COM-Schnittstellenmember aufzurufen. Diese Komplexität hat das Programmieren von Verzeichnisdiensten für viele Entwickler zu einem frustrierenden Unterfangen gemacht.
Noch dazu gäbe es weitere Schwierigkeiten bei dem Versuch, das gleiche Benutzerkonto in AD LDS oder in der lokalen SAM-Datenbank zu erstellen. AD LDS und Active Directory-Domänendienste verwenden eine unterschiedliche Methode zum Aktivieren von Benutzerkonten (das msds-userAccountDisabled-Attribut in AD LDS anstatt des userAccountControl-Attributs in Active Directory-Domänendiensten). Der SAM-Speicher hingegen erfordert, dass Sie ein ADSI-Schnittstellenmember aufrufen. Diese fehlende Konsistenz bei den Kontoverwaltungsfeatures der drei Verzeichnisspeicher war beim Entwurf eine der wichtigsten Herausforderungen, die der AccountManagement-Namespace bewältigen musste. Jetzt können Sie dadurch, dass Sie den zum Erstellen des UserPrincipal-Objekts verwendeten „PrincipalContext“ einfach ändern, leicht die Verzeichnisspeicher wechseln und somit über eine konsistente Gruppe von Kontoverwaltungsfeatures verfügen.
Der in .NET Framework 2.0 eingeführte Protocols-Namespace behandelt keine dieser Anforderungen. Sein Zweck ist, Programmierern von LDAP (Systemebene) eine leistungsfähigere und flexiblere API zum Erstellen von LDAP-basierten Anwendungen bereitzustellen. Der Protocols-Namespace bietet jedoch im Vergleich zum LDAP-Modell noch weniger Abstraktion als System.DirectoryServices und trägt in keiner Weise zum Vereinfachen der Unterschiede zwischen den verschiedenen Verzeichnissen bei. Außerdem ist er nicht für die Verwendung mit der lokalen SAM-Datenbank vorgesehen (die kein LDAP-Verzeichnis darstellt). Die Onlinecodebeispiele für diesen Artikel umfassen ein Beispiel, das dem AccountManager-Beispiel in Abbildung 3 ähnelt. Es führt die gleiche Aufgabe durch, benötigt dafür jedoch das Dreifache an Codezeilen.
Der „PrincipalContext“ in Abbildung 5 zeigt die auf einen Computer bezogene ContextType-Enumeration, um auf einen SAM-Speicher abzuzielen. Der nächste Parameter zielt auf den eigentlichen Computer mithilfe des Namens (oder der IP-Adresse) ab, und die letzten zwei Werte stellen die Anmeldeinformationen eines Kontos bereit, das die Kontoerstellung durchführen kann.
PrincipalContext principalContext = new PrincipalContext(
    ContextType.Machine. "computer01", "adminUser", "adminPassword");
   
UserPrincipal user = new UserPrincipal(principalContext,
    "User1Acct", "pass@1w0rd01", true);

//Note the difference in attributes when accessing a different store
//the attributes appearing in IntelliSense are not derived from the
//underlying store
user.Name = "User One";
user.Description = "User One";
user.ExpirePasswordNow();
user.Save();
Die Namens- und Beschreibungseigenschaften werden festgelegt, weil der SAM-Speicher die Attribute „givenName“, „sn“ oder „displayName“ nicht enthält, so wie das in Active Directory-Domänendiensten der Fall ist. Obwohl „AccountManagement“ versucht, eine gleichmäßige Erfahrung für alle drei Verzeichnisspeicher zu bieten, gibt es Unterschiede in den zugrunde liegenden Modellen. Es löst jedoch eine „InvalidOperationException“ aus, wenn Sie versuchen, ein Attribut abzurufen, das im zugrunde liegenden Speicher nicht verfügbar ist.
In Abbildung 3 und Abbildung 5 sind zwei Beispiele für das Erstellen von Benutzerkonten dargestellt. Sie zeigen das konsistente Programmiermodell im AccountManagement-Namespace beim Betrieb eines beliebigen Speichers. Der „PrincipalContext“ in Abbildung 6 verwendet „ContextType.ApplicationDirectory“, um auf einen AD LDS-Speicher abzuzielen (siehe Abbildung 3). Der nächste Parameter zeigt den AD LDS-Server. In diesem Fall ist „sea-dc-02.fabrikam.com“ der vollqualifizierte Domänenname (Fully Qualified Domain Name, FQDN) des Servers, der die AD LDS-Instanz hostet, und die Instanz fragt die SSL-Kommunikation auf Port 50001 ab. Beachten Sie, dass der Codedownload die Nicht-SSL-Kommunikation über Port 50000 verwendet. Dies ist zwar normalerweise nicht sicher, für Ihre Tests jedoch in Ordnung.
PrincipalContext principalContext = new PrincipalContext(
    ContextType.ApplicationDirectory,
    "sea-dc-02.fabrikam.com:50001",
    "ou=ADAM Users,o=microsoft,c=us",
    ContextOptions.SecureSocketLayer | ContextOptions.SimpleBind,
    "CN=administrator,OU=ADAM Users,O=Microsoft,C=US",
    "P@55w0rd0987");

UserPrincipal user = new UserPrincipal(principalContext,
    "User1Acct", "pass@1w0rd01", true);

user.GivenName = "User";
user.Surname = "One";

user.Save();

Der nächste Parameter bestimmt den Container, in dem Sie einen CRUD-Vorgang (Create/Read/Update/Delete) durchführen wollen. In diesem Beispiel wird ein in AD LDS gespeicherter Benutzer zum Durchführen der CRUD-Vorgänge angegeben. Deshalb muss eine einfache LDAP-Bindung verwendet werden, die zur Sicherheit mit SSL kombiniert wird. Obwohl AD LDS die sichere DIGEST-Authentifizierung systemeigen unterstützt, wird sie von ADSI nicht unterstützt. Auch hier ist das vorliegende Beispiel praktisch identisch mit den zwei vorherigen Beispielen, wobei sich nur der „PrincipalContext“ wesentlich unterscheidet.
Der AccountManagement-Namespace stellt einen umfassenden Satz von Kontoverwaltungsfeatures wie Kennwortablauf und Kontoentsperrung bereit. Aus Platzgründen können nicht alle vorgeführt werden, doch die Quintessenz ist, dass sie gleichmäßig und zuverlässig verzeichnisspeicherübergreifend funktionieren, und sie tragen dazu bei, dass das Implementieren solcher Funktionen zu einer mühelosen Angelegenheit wird.

Erstellen von Gruppen und Computern
Sie haben gesehen, dass das Erstellen eines Benutzerkontos in den einzelnen DirectoryServices-Speichern einfach und konsistent ist. Diese Konsistenz umfasst auch das Erstellen der anderen beiden unterstützten DirectoryServices-Objekte, nämlichen Gruppen und Computer. Wie die UserPrincipal-Klasse erben die GroupPrincipal- und ComputerPrincipal-Klassen von der abstrakten Klasse „Principal“ und funktionieren ähnlich. Zum Erstellen einer Gruppe namens „Group01“ in den Active Directory-Domänendiensten, AD LDS oder der SAM-Kontodatenbank können Sie zum Beispiel diesen Code verwenden:
GroupPrincipal group = new GroupPrincipal(principalContext,
    "Group01");
group.Save();
In jedem Fall sind die Unterschiede in der PrincipalContext-Klasse enthalten, die den Kontext mit den verschiedenen Speichern einrichtet. Der zum Erstellen eines Computerobjekts verwendete Code folgt einem ähnlichen Muster für das Erstellen eines Prinzipalobjekts mithilfe eines Kontexts und dem anschließenden Speichern des Objekts im Ziel des Prinzipalobjekts:
ComputerPrincipal computer = new ComputerPrincipal(domainContext);
computer.DisplayName = "Computer1";
computer.SamAccountName = "Computer1$";
computer.Enabled = true;
computer.SetPassword("p@ssw0rd01");
computer.Save();
Auch hier sorgt „AccountManagement“ für die Vereinigung des Interaktionsmodells für alle unterstützten Identitätsspeicher. Dieses Beispiel stellt die richtige Syntax für das Erstellen eines Computerobjekts dar, das mit der Domäne verknüpft werden kann (was ein nachgestelltes $ im SamAccountName-Attribut erfordert), den Anzeigenamen und allgemeinen Namen jedoch festlegt, damit das $ nicht eingeschlossen ist. Beachten Sie Folgendes: Da die SAM-Datenbank und AD LDS die Computerklasse nicht enthalten, ermöglicht „AccountManagement“ nur das Erstellen dieses Objekttyps innerhalb eines domänenbasierten „PrincipalContext“. Darüber hinaus enthalten nur die Active Directory-Domänendienste eine Reihe von Gruppenbereichen – Global, Universal und Lokal (in Domäne) – und sowohl Sicherheits- als auch Verteilergruppen. Deshalb stellt die GroupPrincipal-Klasse nullfähige Eigenschaften bereit, die Ihnen ermöglichen, diese Werte falls notwendig festzulegen.

Verwalten der Gruppenmitgliedschaft
Der AccountManagement-Namespace vereinfacht außerdem das Verwalten der Gruppenmitgliedschaft. Vor „AccountManagement“ gab es viele Eigenheiten und Inkonsistenzen in den Verwaltungsgruppen für die verschiedenen Speicher. Das Verwalten von Gruppen mit vielen Mitgliedern war von der Programmsteuerung her schwierig. Außerdem musste COM-Interop verwendet werden, um die SAM-Gruppenmitgliedschaft zu verwalten, und LDAP-Attribute, um Active Directory-Domänendienste und AD LDS-Gruppen zu verwalten. Doch jetzt können Sie mit der Members-Eigenschaft der GroupPrincipal-Klasse die Mitgliedschaften einer Gruppe aufzählen und ihre Mitglieder verwalten. Das Ganze funktioniert.
Ein anderer scheinbar einfacher Vorgang, der in Wirklichkeit aber ziemlich schwierig ist, ist das Abrufen aller Gruppen, denen ein Benutzer angehört. „AccountManagement“ stellt hierzu mehrere Methoden bereit. Die Prinzipalbasisklasse umfasst zwei GetGroups-Methoden und zwei IsMemberOf-Methoden, die jeweils die Gruppenmitgliedschaft eines Prinzipaltyps abrufen, um zu überprüfen, ob der Prinzipal Mitglied einer Gruppe ist. Außerdem stellt UserPrincipal eine besondere GetAuthorizationGroups-Methode bereit, die die vollständig erweiterte Sicherheitsgruppenmitgliedschaft eines UserPrincipal-Typs zurückgibt. In Abbildung 7 wird dargestellt, wie sich die GetAuthorizationGroups-Methode verwenden lässt.
string userName = "user1Acct";

// find the user in the identity store
UserPrincipal user =
    UserPrincipal.FindByIdentity(
        adPrincipalContext, 
        userName);

// get the groups for the user principal and
// store the results in a PrincipalSearchResult object
PrincipalSearchResult<Principal> results = 
    user.GetAuthorizationGroups();

// display the names of the groups to which the
// user belongs
Console.WriteLine("groups to which {0} belongs:", userName);
foreach (Principal result in results)
{
    Console.WriteLine("name: {0}", result.Name);
}

Bei einem weiteren komplizierten Vorgang, der mit „AccountManagement“ erleichtert wird, geht es um das Erweitern der Gruppenmitgliedschaft auf vertrauenswürdige Domänen oder mit fremden Sicherheitsprinzipalen. Die GetGroups(PrincipalContext)-Methode für die Prinzipalklasse übernimmt hier den Großteil der Schwerarbeit.

Finden von Objekten
Eine andere Aufgabe, mit der Programmierer oft zu kämpfen haben, ist das Finden von Objekten im Verzeichnis. Obwohl es sich bei LDAP um keine besonders komplizierte Abfragesprache handelt, ist sie im Vergleich zu den Syntaxen, die Entwickler routinemäßig bearbeiten, oft eher unbekannt. Hinzu kommt, dass es selbst dann, wenn Sie die Grundlagen von LDAP kennen, oft schwierig für Sie herauszufinden ist, wie es zum Durchführen allgemeiner Aufgaben verwendet werden kann.
Auch hier lassen sich diese Aufgaben mithilfe von „AccountManagement“ mühelos erledigen, da Sie Objekte mit der FindByIdentity-Methode finden können. Diese Methode ist Teil der abstrakten Klasse „Prinzipal“, von der die UserPrincipal-, GroupPrincipal- und ComputerPrincipal-Klassen erben. Daher ist es außerordentlich nützlich, „FindByIdentity“ zum Suchen einer dieser Prinzipaltypen zur Verfügung zu haben.
„FindByIdentity“ enthält zwei Überladungen, die beide einen „PrincipalContext“ und einen Wert für die Suche einsetzen. Für den Wert können Sie einen der unterstützten Identitätstypen angeben: „SamAccountName“, „Name“, „UserPrincipalName“, „DistinguishedName“, „SID“ oder „GUID“. Die zweite Überladung ermöglicht Ihnen außerdem, explizit den Identitätstyp zu definieren, den Sie als den Wert angeben.
Angefangen mit der einfacheren Überladung können Sie FindByIdentity folgendermaßen verwenden, um das Benutzerkonto zurückzugeben, das in den vorherigen Beispielen erstellt wurde:
UserPrincipal user = UserPrincipal.FindByIdentity(principalContext, "user1Acct");
Wenn Sie einen Kontext erstellt haben (gespeichert im principalContext-Objekt), verwenden Sie die FindByIdentity-Methode, um das Prinzipalobjekt abzurufen, das in diesem Fall ein „UserPrincipal“ ist. Nach dem Einrichten des Kontexts für einen unterstützten Identitätsspeicher ist der Code für die Suche der Identität immer gleich.
Der zweite FindByIdentity-Konstruktor ermöglicht Ihnen, das anzugebende Identitätsformat explizit auszudrücken. Wenn Sie diesen Konstruktor verwenden, müssen Sie das Format des Werts an den von Ihnen angegebenen Identitätstyp anpassen. Zum Beispiel gibt dieser Code einen „UserPrincipal“ durch Verwendung seines definierten Namens ordnungsgemäß zurück, vorausgesetzt, dass das Objekt im Verzeichnis und im angegebenen Ort vorhanden ist:
UserPrincipal user = UserPrincipal.FindByIdentity(
    adPrincipalContext, 
   IdentityType.DistinguishedName,
   "CN=User1Acct,OU=TechWriters,DC=FABRIKAM,DC=COM");
Im Gegensatz dazu gibt dieser Code keinen „UserPrincipal“ zurück, da die IdentityType-Enumeration ein DistinguishedName-Format angibt, der Wert jedoch nicht in diesem Format vorliegt:
UserPrincipal user = UserPrincipal.FindByIdentity(
    adPrincipalContext,
   IdentityType.DistinguishedName,
   "user1Acct");
Das Format ist wichtig. Wenn Sie sich zum Beispiel für die Verwendung der GUID- oder SID-IdentityTypes entscheiden, müssen Sie das Standard-COM GUID-Format bzw. das SDDL-Format (Security Descriptor Description Language) für den Wert verwenden. Der Codedownload für diesen Artikel stellt zwei Methoden („FindByIdentityGuid“ und „FindByIdentitySid“) bereit, die das richtige Format aufweisen. Beachten Sie, dass Sie die GUID- oder SID-Werte in diesen Methoden ändern müssen, um eine Entsprechung in Ihrem Verzeichnisspeicher zu finden. Wie im Folgenden veranschaulicht wird, ist es nicht schwierig, eines dieser Formate mithilfe der PrincipalSearcher-Klasse zu erhalten.
Nachdem jetzt ein Prinzipalobjekt gefunden und eine Bindung hergestellt ist, können Sie leicht Vorgänge an ihm durchführen. Zum Beispiel können Sie einer Gruppe einen Benutzer hinzufügen, etwa so:
// get a user principal
UserPrincipal user = 
    UserPrincipal.FindByIdentity(adPrincipalContext, "User1Acct");
// get a group principal
GroupPrincipal group = 
    GroupPrincipal.FindByIdentity(adPrincipalContext, "Administrators");
// add the user
group.Members.Add(user);
// save changes to directory
group.Save();
Hier wird die FindByIdentity-Methode verwendet, um zuerst einen Benutzer zu finden und dann eine Gruppe. Der Code ruft diese Prinzipalobjekte ab. Anschließend wird die Add-Methode der Members-Eigenschaft der Gruppe aufgerufen, um der Gruppe den Benutzerprinzipal hinzuzufügen. Schließlich wird die Save-Methode der Gruppe aufgerufen, um die Änderung im Verzeichnis zu speichern.

Suche von Entsprechungen
Sie können auch die leistungsfähige Query-by-Example (QBE)-Einrichtung und die PrincipalSearcher-Klasse verwenden, um ein Objekt zu suchen, das auf den definierten Kriterien basiert. Im Folgenden erfahren Sie mehr über QBE und die PrincipalSearcher-Klasse. Zunächst soll jedoch ein einfaches Suchbeispiel untersucht werden. In Abbildung 8 wird dargestellt, wie Sie alle Benutzerkonten finden können, die mit dem name/cn-Präfix „user“ beginnen und deaktiviert sind.
// create a principal object representation to describe
// what will be searched 
UserPrincipal user = new UserPrincipal(adPrincipalContext);

// define the properties of the search (this can use wildcards)
user.Enabled = false;
user.Name = "user*";

// create a principal searcher for running a search operation
PrincipalSearcher pS = new PrincipalSearcher();

// assign the query filter property for the principal object 
// you created
// you can also pass the user principal in the 
// PrincipalSearcher constructor
pS.QueryFilter = user;

// run the query
PrincipalSearchResult<Principal> results = pS.FindAll();

Console.WriteLine("Disabled accounts starting with a name of 'user':");
foreach (Principal result in results)
{
    Console.WriteLine("name: {0}", result.Name);
}

Die PrincipalContext-Variable „adPrincipalContext“ verweist auf eine Active Directory-Domäne, sie könnte jedoch ebenso auf eine AD LDS-Anwendungspartition verweisen. Nach dem Einrichten des Kontexts ist zu beachten, dass der Code ein neues UserPrincipal-Objekt erstellt. Dies ist eine Speicherdarstellung des Prinzipals für den Suchvorgang. Legen Sie nach Erstellung dieses Prinzipals die Eigenschaften zur Eingrenzung der Suchergebnisse fest. Die nächsten zwei Codezeilen veranschaulichen einige Beschränkungen, die Sie festlegen können – alle deaktivierten Benutzerkonten, bei denen der Benutzername mit einem Wert beginnt. Beachten Sie, dass der Eigenschaftswert für das Name-Attribut Platzhalter unterstützt.
Wenn Sie bereits mit dem LDAP-Dialekt für das Einrichten von Suchfiltern vertraut sind, werden Sie sofort erkennen, warum QBE eine Neuerung und die intuitivere Alternative ist. Mit QBE richten Sie ein Beispielobjekt ein, das Sie anschließend für den Abfragevorgang verwenden. Um zu verdeutlichen, dass QBE einfacher ist als der typische DirectoryServices-Suchdialekt, finden Sie hier den LDAP-Dialekt zum Einrichten einer Filterentsprechung für das QBE-Objekt, das in Abbildung 8 erstellt wird:
(&(objectCategory=person)(objectClass=user)(name=user*)(userAccount
Control:1.2.840.113556.1.4.803:=2))
Wie Sie sehen, ist der LDAP-Dialekt weitaus komplizierter, und er funktioniert bei AD LDS nicht, weil das Active Directory-LDS-Benutzerschema das msDS-UserAccountDisabled-Attribut anstatt des im LDAP-Dialekt dargestellten userAccountControl-Attributs verwendet. Auch hier werden diese Unterschiede von „AccountManagement“ im Hintergrund behandelt.
Nach dem Einrichten des in Abbildung 8 dargestellten QBE-Objekts wird ein PrincipalSearcher-Objekt erstellt und seine vom Prinzipalobjekt zuvor im Code erstellte QueryFilter-Eigenschaft zugeordnet. Beachten Sie, dass Sie auch den Benutzerprinzipal im PrincipalSearcher-Konstruktor weiterleiten können, anstatt die QueryFilter-Eigenschaft einzurichten. Anschließend wird die Abfrage ausgeführt, indem die FindAll-Methode von „PrincipalSearcher“ aufgerufen und die zurückgegebenen Ergebnisse zur generischen PrincipalSearchResult-Liste zugeordnet werden. Die PrincipalSearchResult-Liste speichert die zurückgegebenen Prinzipalobjekte. Abschließend zählt der Code die Liste der Prinzipale auf und zeigt das Name-Attribut der einzelnen zurückgegebenen Prinzipale an.
Beachten Sie, dass QBE für Verweisattribute nicht funktioniert. Das heißt, Attribute, die nicht Eigentum des QBE-Objekts sind, können nicht dazu verwendet werden, Ihre Speicherdarstellung des Objekts zu konfigurieren.
Sie können in der Foreach-Schleife viel mehr erreichen. Zum Beispiel können Sie die deaktivierten Benutzerkonten aktivieren oder löschen. Wenn Sie nur auf Lesevorgänge bedacht sind, bedenken Sie Folgendes: Wenn Sie auf einen anderen Identitätsspeicher verweisen, müssen die zurückgegebenen Attribute in diesem Speicher existieren. Da zum Beispiel ein AD LDS-Benutzer das SamAccountName-Attribut nicht enthält, würde es keinen Sinn ergeben, dieses Attribut in den Ergebnissen zurückgeben zu wollen.

Schwierige Suchvorgänge leicht gemacht
Es gibt weitere leistungsfähige FindBy-Methoden, die in Kombination mit der PrincipalSearchResult-Klasse Informationen zu Benutzer- und Computerprinzipalen abrufen können, die andernfalls nur schwer abrufbar sind. In Abbildung 9 wird vorgeführt, wie Sie die Namen aller Benutzer abrufen, die heute ihre Kennwörter geändert haben. In diesem Beispiel werden die FindByPasswordSetTime-Methode und die PrincipalSearchResult-Klasse verwendet. Ohne „AccountManagement“ ist dieser Vorgang kompliziert, weil das zugrunde liegende pwdLastSet-Attribut im Verzeichnis als eine große Ganzzahl gespeichert ist.
// get today's date
DateTime dt = DateTime.Today;

// run a query
PrincipalSearchResult<Principal> results = 
    UserPrincipal.FindByPasswordSetTime(
        adPrincipalContext, 
        dt, 
        MatchType.GreaterThanOrEquals); 

Console.WriteLine("users whose password was set on {0}", 
    dt.ToShortDateString());
foreach (Principal result in results)
{
    Console.WriteLine("name: {0}", result.Name);
}

Der Codedownload für diesen Artikel enthält Beispiele für die Verwendung anderer FindBy-Methoden. All diese Methoden funktionieren so ähnlich wie diese in Abbildung 9 dargestellte Methode.
Bei FindBy-Methoden handelt es sich um praktische Verknüpfungen mit Informationen, die andernfalls schwer abrufbar sind. Sie sind jedoch nicht geeignet, wenn Sie die Ergebnisse mithilfe der QBE-Einrichtung weiterhin filtern müssen. Hier ist der feine Unterschied wichtig, dass das zugeordnete Attribut schreibgeschützt ist und deshalb nicht für ein QBE-Objekt festgelegt werden kann, genauso wie es von einem Benutzer nicht für ein Objekt festgelegt werden kann, auf das QBE verweist. Sie verwenden QBE, indem Sie die gleichwertige schreibgeschützte Eigenschaft in Ihrem Beispielprinzipalobjekt zusammen mit der AdvancedSearchFilter-Eigenschaft verwenden. Weitere Informationen dazu folgen später. In Abbildung 10 werden weitere FindBy-Methoden aufgelistet und die gleichwertigen schreibgeschützten Eigenschaften dargestellt, die Sie statt der FindBy-Methode in einer Suche verwenden können.

Methodenname Schreibgeschützte Eigenschaft Beschreibung
FindByLogonTime LastLogonTime Konten, die sich innerhalb des angegebenen Zeitraums angemeldet haben.
FindByExpirationTime AccountExpirationDate Innerhalb des angegebenen Zeitraums abgelaufene Konten.
FindByPasswordSetTime LastPasswordSetTime Konten, deren Kennwort innerhalb des angegebenen Zeitraums festgelegt wurde.
FindByLockoutTime AccountLockoutTime Innerhalb des angegebenen Zeitraums gesperrte Konten.
FindByBadPasswordAttempt LastBadPasswordAttempt Innerhalb des angegebenen Zeitraums erfolgte Eingaben falscher Kennwörter.
Keine gleichwertige Methode BadLogonCount Konten, deren Anmeldung trotz Ausschöpfung der angegebenen Anzahl von Eingabeversuchen fehlgeschlagen ist.
Sie können für eine schreibgeschützte Eigenschaft beim Konfigurieren einer QBE keinen Wert festlegen. Wie kann also die Eigenschaft in einem Suchvorgang verwendet werden? Sie können einen Ergebnissatz abrufen und dann einen bedingten Test mithilfe der schreibgeschützten Eigenschaft beim Aufzählen des Ergebnissatzes durchführen. Bedenken Sie dabei, dass sich dieser Ansatz für möglicherweise große Resultsets nicht eignet, da der Code zuerst die für die schreibgeschützte Eigenschaft ungefilterten Ergebnisse abrufen und dann den zurückgegebenen Resultset nach der schreibgeschützten Eigenschaft filtern muss. Die PrincipalSearchEx6v2-Methode im Codedownload führt diesen nicht gerade optimalen Ansatz vor.
Das Team für Verzeichnisdienste hat diese QBE-Einschränkung durch Hinzufügen der AdvancedSearchFilter-Eigenschaft zur AuthenticablePrincipal-Klasse behandelt. AdvancedSearchFilter ermöglicht Ihnen, aufgrund von schreibgeschützten Eigenschaften zu suchen und diese anschließend mit anderen Eigenschaften, die Sie mithilfe der QBE-Methode festlegen können, zu kombinieren. In Abbildung 11 wird vorgeführt, wie Sie die schreibgeschützte LastBadPasswordAttempt-Eigenschaft der UserPrincipal-Klasse verwenden können, um eine Liste der Benutzer zurückzugeben, die heute ein Kennwort falsch eingegeben haben.
DateTime dt = DateTime.Today;

// create a principal object representation to describe
// what will be searched 
UserPrincipal user = new UserPrincipal(adPrincipalContext);

user.Enabled = true;

// define the properties of the search (this can use wildcards)
user.Name = "*";

//add the LastBadPasswordAttempt >= Today to the query filter
user.AdvancedSearchFilter.LastBadPasswordAttempt
    (dt, MatchType.GreaterThanOrEquals);

// create a principal searcher for running a search operation
// and assign the QBE user principal as the query filter
PrincipalSearcher pS = new PrincipalSearcher(user);

// run the query
PrincipalSearchResult<Principal> results = pS.FindAll();

Console.WriteLine("Bad password attempts on {0}:", 
    dt.ToShortDateString());
foreach (UserPrincipal result in results)
{
    Console.WriteLine("name: {0}, {1}",
           result.Name,
           result.LastBadPasswordAttempt.Value);
}


Authentifizieren von Benutzern
Entwickler, die verzeichnisbasierte Anwendungen erstellen, müssen häufig die Anmeldeinformationen der im Verzeichnis gespeicherten Benutzer authentifizieren, vor allem wenn sie AD LDS verwenden. Vor .NET Framework 3.5 erledigten Programmierer diese Aufgabe, indem sie die DirectoryEntry-Klasse in System.DirectoryServices verwendeten, um intern einen LDAP-Bindungsvorgang zu erzwingen. Hier ist jedoch Vorsicht geboten: Es ist unglaublich einfach, schlechte Versionen dieses Codes zu erstellen, die nicht sicher, langsam oder einfach schwerfällig sind. Darüber hinaus wurde ADSI selbst nicht für diese Art von Vorgang entworfen und kann bei starker Nutzung aufgrund der Weise, wie LDAP-Verbindungen intern zwischengespeichert werden, fehlschlagen.
Wie bereits erläutert, enthält die System.DirectoryServices.Protocol-Assembly in .NET Framework 2.0 untergeordnete LDAP-Klassen, die eine verbindungsbasierte Programmiermetapher verwenden. Dieser Entwurf ermöglicht Ihnen, die inhärenten Einschränkungen in ADSI zu überwinden, erfordert jedoch, dass Sie komplizierteren Code erstellen müssen.
In .NET Framework 3.5 bietet „AccountManagement“ sowohl die Leistung als auch die Benutzerfreundlichkeit, die Programmierern in einer beliebigen Arbeitsumgebung von der ActiveDirectoryMembershipProvider-Implementierung in ASP.NET geboten wird. Zusätzlich ermöglicht der AccountManagement-Namespace ggf. das Authentifizieren der Anmeldeinformationen anhand der lokalen SAM-Datenbank.
Die zwei ValidateCredentials-Methoden für die PrincipalContext-Klasse stellen die Anmeldeinformationsprüfung bereit. Zuerst erstellen Sie eine Instanz eines „PrincipalContext“, indem Sie das Verzeichnis verwenden, das überprüft werden soll, und geben die passenden Optionen an. Nach dem Abrufen des Kontexts testen Sie, ob „ValidateCredentials“ aufgrund der bereitgestellten Werte für den Benutzernamen und das Kennwort wahre oder falsche Werte zurückgibt. In Abbildung 12 wird ein Beispiel für das Authentifizieren eines Benutzers in AD LDS angeführt.
// establish context with AD LDS
PrincipalContext ldsContext = 
    new PrincipalContext(
        ContextType.ApplicationDirectory, 
        "sea-dc-02.fabrikam.com:50000", 
        "ou=ADAM Users,O=Microsoft,C=US");

// determine whether a user can validate to the directory
Console.WriteLine(
    ldsContext.ValidateCredentials(
        "user1@adam", 
        "Password1", 
        ContextOptions.SimpleBind + 
        ContextOptions.SecureSocketLayer));

Dieser Ansatz ist am nützlichsten, wenn Sie viele verschiedene Sätze von Benutzeranmeldeinformationen schnell und effizient überprüfen wollen. Sie erstellen ein einzelnes PrincipalContext-Objekt für den entsprechenden Verzeichnisspeicher und verwenden diese Objektinstanz für jeden Aufruf an „ValidateCredentials“ wieder. Der „PrincipalContext“ kann die Verbindung zum Verzeichnis wiederverwenden, was zu guter Leistung und Skalierbarkeit führt. Aufrufe an „ValidateCredentials“ sind threadsicher. Deshalb kann Ihre Instanz für diesen Vorgang threadübergreifend verwendet werden. Es sollte beachtet werden, dass die Anmeldeinformationen, die zum Erstellen des „PrincipalContext“ verwendet werden, sich durch die Aufrufe an „ValidateCredentials“ nicht ändern – der Kontext und der Methodenaufruf behalten separate Verbindungen bei.
Standardmäßig verwendet „AccountManagement“ die sichere Windows-Aushandlungsauthentifizierung und versucht, beim Durchführen einer einfachen Bindung anhand von AD LDS SSL zu verwenden. Es ist empfehlenswert, dass Sie die Art der durchzuführenden Authentifizierung und des zu verwendenden Verbindungsschutzes (falls zutreffend) immer explizit angeben. Mit den Standardeinstellungen können Sie aber nichts falsch machen.
Active Directory-Domänendienste in Windows Server® 2003 und höher sowie AD LDS umfassen die schnelle gleichzeitige Bindung, die für Vorgänge der Hochleistungsauthentifizierung entworfen wurde. Sie überprüft das Kennwort eines Benutzers, ohne tatsächlich ein Sicherheitstoken für den Benutzer zu erstellen. Im Unterschied zu einem normalen Bindungsvorgang mit schneller gleichzeitiger Bindung bleibt der Status der LDAP-Verbindung ungebunden. Sie können mit der schnellen gleichzeitigen Bindung wiederholt Bindungsvorgänge für die gleiche Verbindung durchführen und einfach überprüfen, ob eine fehlgeschlagene Kennworteingabe vorliegt. Dieses Feature ist keine über ADSI oder System.DirectoryServices verfügbare Option, wird aber als Option im Protocols-Namespace verfügbar gemacht.
„AccountManagement“ verwendet nach Möglichkeit die schnelle gleichzeitige Bindung und aktiviert diese Option automatisch. Aus diesem Grund wird in Abbildung 1 auch die AccountManagement-Ebene über der Protokollebene angezeigt. Beachten Sie, dass dies nur im einfachen Bindungsmodus funktioniert, der Nur-Text-Anmeldeinformationen im Netzwerk weiterleitet. Deshalb sollte die schnelle gleichzeitige Bindung zur Sicherheit immer mit SSL kombiniert werden.

Erweiterbarkeitsmodell
Ein anderer Bereich, in dem „AccountManagement“ wirklich herausragt, ist das Erweiterbarkeitsmodell. Viele Entwickler werden sich dafür entscheiden, die verschiedenen prinzipalabgeleiteten Klassen zum Erstellen benutzerdefinierter Bereitstellungssysteme sowohl für Active Directory-Domänendienste als auch AD LDS zu verwenden. In vielen Fällen (besonders mit AD LDS) fügt eine Organisation einem Verzeichnis benutzerdefinierte Schemaerweiterungen hinzu, um ihre eigenen Metadaten für Benutzer und Gruppen zu unterstützen.
Mithilfe des objektorientierten .NET Framework-Entwurfs und attributbasierter erweiterbarer Metadaten vereinfacht „AccountManagement“ das Erstellen benutzerdefinierter Sicherheitsprinzipalklassen, die Ihr benutzerdefiniertes Schema unterstützen. Dadurch, dass einfach von einer der prinzipalabgeleiteten Klassen geerbt wird und die Klasse und Eigenschaften mit den passenden Attributen gekennzeichnet werden, kann die von Ihnen erstellte benutzerdefinierte Klasse diese Verzeichnisse sowie die Attribute, die bereits von den integrierten Typen unterstützt werden, lesen und bearbeiten.
Ein hier erwähnenswerter wichtiger Unterschied ist, dass die Erweiterbarkeitsmethode, die von „AccountManagement“ bereitgestellt wird, für die Verwendung durch Sicherheitsprinzipale entworfen wurde, die in Active Directory-Domänendiensten oder AD LDS gespeichert werden. Sie konzentriert sich nicht auf Nicht-Microsoft-LDAP-Verzeichnisse. Wenn Sie ein Framework für die Bereitstellung von Nicht-Microsoft-LDAP-Verzeichnissen erstellen möchten, sollten Sie die untergeordneten Klassen im Protocols-Namespace verwenden. (Außerdem ist das Erweiterbarkeitsmodell nicht für die Verwendung mit lokalen SAM-Konten vorgesehen, da das SAM-Schema nicht erweiterbar ist.)
Ziehen Sie ein AD LDS-Verzeichnis in Betracht, das die Standard-LDAP-Benutzerklasse für das Speichern von Sicherheitsprinzipalen für eine Anwendung verwendet. Darüber hinaus ist das LDAP-Verzeichnisschema so erweitert, dass ein besonderes Attribut für das Identifizieren von Benutzerobjekten namens „msdnsubscriberID“ unterstützt wird. In Abbildung 13 wird vorgeführt, wie Sie eine benutzerdefinierte Klasse erstellen, die Benutzerobjekte sowie auch Erstellungs-, Lese und Schreibvorgänge anhand dieses Attributs bereitstellen kann.
[DirectoryObjectClass("user")]
[DirectoryRdnPrefix("CN")]
class MsdnUser : UserPrincipal
{
    public MsdnUser(PrincipalContext context)
        : base(context) { }

    public MsdnUser(
        PrincipalContext context,
        string samAccountName,
        string password,
        bool enabled
        )
        : base(
           context,
           samAccountName,
           password,
           enabled
           )
    {
    }

    [DirectoryProperty("msdn-subscriberID")]
    public string MsdnSubscriberId
    {
        get
        {
            object[] result = this.ExtensionGet("msdn-subscriberID");
            if (result != null) {
                return (string)result[0];
            }
            else {
                return null;
            }
        }
        set { this.ExtensionSet("msdn-subscriberID", value); }
    }
}

Beachten Sie, dass der Code von der UserPrincipal-Klasse erbt und mit zwei Attributen versehen ist: „DirectoryObjectClass“ und „DirectoryRdnPrefix“. Beide Attribute sind für die Prinzipalerweiterungsklassen erforderlich. Das DirectoryObjectClass-Attribut bestimmt den Wert, den der unterstützte Speicher (Active Directory-Domänendienste oder AD LDS) für das objectClass-Verzeichnisattribut verwendet, wenn es Instanzen dieses Objekts im Verzeichnis erstellt. Es handelt sich hierbei weiterhin um die standardmäßige AD LDS-Benutzerklasse, doch in Wirklichkeit könnte es alles Mögliche sein. Das DirectoryRdnPrefix-Attribut bestimmt das RDN-Attribut (Relative Distinguished Name), das zum Benennen von Objekten dieser Klasse im Verzeichnis verwendet werden soll. In den Active Directory-Domänendiensten können Sie das RDN-Präfix nicht ändern – es ist immer „CN“ für die Sicherheitsprinzipalklassen. AD LDS bietet jedoch mehr Flexibilität, und Sie können nach Wunsch einen anderen RDN verwenden.
Die hier verwendete Klasse enthält eine Eigenschaft namens „MsdnSubscriberID“, die eine Zeichenfolge zurückgibt. Diese Klasse ist mit dem DirectoryProperty-Attribut gekennzeichnet, das das LDAP-Schemaattribut angibt, das zum Speichern des Eigenschaftswerts verwendet wird. Das zugrunde liegende Framework verwendet diesen Wert für das Optimieren der Suchvorgänge anhand dieses Prinzipaltyps.
Die Get- und Set-Implementierungen dieser Eigenschaft verwenden die geschützten ExtensionGet- und ExtensionSet-Methoden der Prinzipalbasisklasse zum Lesen und Schreiben von Werten im zugrunde liegenden Eigenschaftscache. Diese Methoden unterstützen das Speichern von Werten im Speicher für Objekte, die noch nicht im Datenbank-/Identitätsspeicher gespeichert wurden. Außerdem unterstützen diese Methoden den Lese- und Schreibvorgang für Werte von vorhandenen Objekten. Da LDAP-Verzeichnisse Attribute verschiedener Typen unterstützen und zulassen, dass ein Attribut mehrere Werte enthält, verwenden diese Methoden den Typ „object[]“ für das Lesen und Schreiben von Werten. Diese Flexibilität ist erfreulich, doch wenn Sie einen stark typisierten skalaren Zeichenfolgewert zusätzlich zu einem Array von Objekttypen bereitstellen wollen, müssen Sie etwas mehr Arbeit investieren, wie unsere Implementierung veranschaulicht. Im Endeffekt erhalten Sie mithilfe der benutzerdefinierten MsdnUser-Klasse eine sehr leicht zu programmierende Schnittstelle.
Die Fähigkeit, stark typisierte Werte zusätzlich zum Verzeichnisschema bereitzustellen, ist eines der nützlichsten Features dieses Erweiterbarkeitsmodells. Über einfache Zeichenfolgetypen hinaus können Sie das von .NET Framework angebotene umfangreiche Typsystem verwenden, um beispielsweise das jpgPhoto-Attribut der Active Directory-Domänendienste als ein „System.Drawing.Image“ oder einen „System.IO.Stream“ darzustellen statt des standardmäßigen „byte[]“, das Sie in der Regel durch Lesen des Werts von System.DirectoryServices abrufen würden.
Der Codedownload für diesen Artikel stellt noch weitere Beispiele bereit, um diese Funktionen vorzuführen. Er enthält außerdem einige Schemaerweiterungen (über die formatierte Standard-LDIF-Datei „msdnschema.ldf“), die Sie zum Erweitern Ihres Testverzeichnisses mit der MsdnUser-Klasse verwenden können. In der Randleiste „Verzeichnisdienstressourcen“ finden Sie außerdem einige nützliche Links.

Schlussbemerkung
Bei „AccountManagement“ handelt es sich um eine seit langem benötigte Erweiterung des verwalteten Codes zum umfangreichen Programmiermodell für Verzeichnisdienste von Microsoft. Mit dem AccountManagement-Namespace verfügen Entwickler jetzt über einen Satz stark typisierter Prinzipalen für allgemeine CRUD- und Suchvorgänge.
Der Namespace fasst die bewährten Methoden der Verzeichnisdienstprogrammierung zusammen und hilft Ihnen beim Erstellen eines verwalteten Codes, der sicher und äußerst leistungsfähig ist. Darüber hinaus ist „AccountManagement“ erweiterbar und ermöglicht Ihnen, vollständig mit Ihren benutzerdefinierten Verzeichnisobjekten in Active Directory-Domänendiensten und AD LDS zu interagieren.

Joe Kaplan ist in der internen IT-Organisation von Accenture tätig und erstellt Unternehmensanwendungen mithilfe von .NET Framework. Er spezialisiert sich auf Anwendungssicherheit, die verbundene Identitätsverwaltung und das Programmieren von Verzeichnisdiensten, und wurde auf diesem Gebiet als Microsoft MVP anerkannt. Joe Kaplan ist Mitautor des Buchs „The .NET Developer's Guide to Directory Services Programming“ (Addison-Wesley, 2006).

Ethan Wilansky ist MVP für Microsoft-Verzeichnisdienste sowie EDS-Unternehmensarchitekt. Als Teil der Methode „EDS Innovation Engineering“ leitet er ein Entwicklungsteam, das sich derzeit auf das Erstellen benutzerdefinierter SharePoint-Anwendungen konzentriert, und berät EDS im Hinblick auf Lösungen für das Programmieren von Verzeichnisdiensten.

Page view tracker