(0) exportieren Drucken
Alle erweitern

Authentifizierung und Autorisierung

Veröffentlicht: 13. Jan 2004 | Aktualisiert: 29. Jun 2004
Von Rockford Lhotka

Rocky Lhotka erklärt in diesem Artikel, wie Sie die Principal- und Identity-Konzepte in .NET dazu verwenden, benutzerdefinierte Authentifizierung und Autorisierung in Windows- und Webanwendungen zu implementieren.

Download
Auf dieser Seite

 Der "System.Security"-Namespace
 Implementieren der Autorisierung
 Benutzerauthentifizierung
 Schlussfolgerung

Sicherheit ist eine ständige Herausforderung in der Softwareentwicklung. Die meisten von uns streben zwar nicht danach, Sicherheitsexperten zu werden, aber wir werden als Anwendungsentwickler ständig mit Sicherheitsmechanismen konfrontiert, die wir verarbeiten und implementieren müssen.

Das Thema Sicherheit umfasst viele Aspekte; von Verschlüsselung und Codierung bis zur Verwaltung der Benutzeridentität und der häufigeren Authentifizierung und Autorisierung. Dieser Artikel handelt von Authentifizierung und Autorisierung, da sie Bestandteile fast jeder Anwendung sind. Microsoft .NET Framework enthält glücklicherweise Funktionen zum Implementieren beider Features.

Bei der Authentifizierung handelt es sich die Überprüfung der tatsächlichen Identität des Endbenutzers. Für diese Überprüfung werden die Anmeldeinformationen des Benutzers (häufig Benutzername und Kennwort) mit einer Liste von bekannten Daten abgeglichen. Wenn die Anmeldeinformationen korrekt sind, authentifizieren wir den Benutzer, andernfalls nicht.

Die Autorisierung findet nach der Authentifizierung des Benutzers statt und entscheidet über die Berechtigungen des Benutzers innerhalb der Anwendung. Dazu kann u.a. gehören, welche Bildschirme und Felder dem Benutzer angezeigt werden oder für welche dieser Bildschirme der Benutzer über einen schreibgeschützten Zugriff oder einen Schreibzugriff verfügt.

Autorisierung wird normalerweise als eine Sicherheitsfunktion betrachtet. Tatsächlich handelt es sich aber bei der Entscheidung, welcher Benutzer Zugriff auf welche Funktionen erhält, um eine Geschäftsentscheidung. Die Regeln für die Implementierung der Autorisierung sind daher Geschäftsregeln. Der Code für die Autorisierung stellt letzten Endes Geschäftslogik dar und keine Sicherheitslogik.

Die Sicherheitsinfrastruktur muss folglich den Zugriff der Geschäftslogik auf die Benutzeridentität ermöglichen und die Zugehörigkeit von Benutzern zu bestimmten Gruppen oder Rollen entscheiden. Um unsere Geschäftsregeln für die Autorisierung zu implementieren, müssen wir uns darauf verlassen können, dass der Benutzer autorisiert wurde und die dazugehörigen Benutzerdaten geladen wurden und von uns verwendet werden können.

Bei der Definition von Rollen, Gruppen oder anderen Autorisierungskriterien müssen die Teams aus dem Geschäfts- und Sicherheitsbereich der Organisation zusammenarbeiten. Beide Teams müssen einen Satz von Rollen vereinbaren, die für die Geschäftsanwendung erforderlich sind. Diese Rollen müssen in Zusammenarbeit der Teams mit den vorhandenen Rollen abgeglichen werden, die in anderen Anwendungen innerhalb der Organisation verwendet werden.

Wenn jedes Team für sich Rollen im luftleeren Raum erstellt, kann die Verwaltung der Benutzer und Rollen für alle Anwendungen innerhalb einer Organisation sehr komplex werden.

Abbildung 1 zeigt die Beziehung zwischen Authentifizierung, Autorisierung und Anwendungen.

AuthentifizierungundAutorisierung_01.gif

Abbildung 1. Authentifizierung, Autorisierung und Geschäftsanwendungen

Der "System.Security"-Namespace

Das Microsoft .NET Framework enthält den System.Security-Namespace mit verschiedenen Sicherheitsfunktionen. Dazu gehören Kryptografie, die Sicherheitsverwaltung für die .NET-Laufzeit selbst sowie die Authentifizierung und Autorisierung von Benutzern.

Die für die Benutzerauthentifizierung und -autorisierung wichtigen Funktionen sind in System.Security.Principal enthalten. Zur Verwaltung von Benutzeridentitäten ordnet das .NET Framework jedem Thread ein Principal-Objekt zu. Da der gesamte Code in einem Thread ausgeführt wird, kann der gesamte Code auf ein Principal-Objekt zugreifen.

In ASP.NET haben wir Zugriff auf zwei Principal-Objekte. Es gibt ein Principal-Objekt, das mit dem aktuellen Thread verbunden ist, und ein zweites, das durch seine Current.User-Eigenschaft mit dem aktuellen HttpContext verbunden ist. Standardmäßig ist das Principal-Objekt des aktuellen Threads dasselbe wie das des HttpContext.

Das Principal-Objekt ermöglicht eine einfache Implementierung der Autorisierung in den Code, auf Basis einer Liste von Rollen, die dem aktuellen Benutzer zugeordnet sind. Außerdem ermöglicht das Principal-Objekt durch den Zugriff auf ein Identity-Objekt die Bestimmung der Benutzeridentität. Abbildung 2 zeigt die Beziehungen zwischen dem aktuellen Thread und seinen Principal- und Identity-Objekten.

AuthentifizierungundAutorisierung_02.gif

Abbildung 2. Beziehung zwischen dem aktuellen Thread, "Principal"- und "Identity"-Objekten

In einer typischen Windows Forms- oder Web Forms-Anwendung gibt es nur einen Thread, der über zugeordnete Principal- und Identity-Objekte verfügt.

Es gibt mehrere Arten von Principal- und Identity-Klassen, um verschiedene Authentifizierungsarten zu unterstützen. Wir können für eine benutzerdefinierte Authentifizierung sogar eigene benutzerdefinierte Principal- und Identity-Klassen erstellen.

Principal-Klassen implementieren die IPrincipal-Schnittstelle. Diese Schnittstelle stellt grundlegende Autorisierungsdienste zur Verfügung:

Methode

Beschreibung

Identity

Gibt das Identity-Objekt zurück, das mit dem Principal-Objekt verbunden ist

IsInRole

Gibt einen booleschen Wert zurück, der angibt, ob der Benutzer zu einer spezifizierten Rolle gehört

Identity-Klassen implementieren die IIdentity-Schnittstelle. Diese Schnittstelle enthält Eigenschaften, mit denen wir die Identität des Benutzers prüfen können, und wie (oder ob) der Benutzer authentifiziert wurde:

Methode

Beschreibung

AuthenticationType

Gibt eine Zeichenfolge mit der Art der Authentifizierung zurück, durch die der aktuelle Benutzer authentifiziert wurde

IsAuthenticated

Gibt einen booleschen Wert zurück, der angibt, ob der aktuelle Benutzer authentifiziert wurde

Name

Gibt den aktuellen Benutzernamen oder die Benutzer-ID zurück

Wenn wir die Eigenschaften des Identity-Objekts und die IsInRole-Methode des Principal-Objekts kombinieren, erreichen wir eine einfache Implementierung des Autorisierungscodes innerhalb unserer Geschäftsanwendungen.

Implementieren der Autorisierung

Wir verwenden das Principal- und das Identity-Objekt, um den Autorisierungscode zu schreiben.

Verwenden des Thread-"Principal"-Objekts
In einer Windows Forms-Anwendung verwenden wir das dem Thread zugeordnete Principal-Objekt. Wir können daher Code wie den folgenden schreiben:

  If Thread.CurrentPrincipal.Identity.IsAuthenticated Then 
 lblUserName.Text = Thread.CurrentPrincipal.Identity.Name 
 If Thread.CurrentPrincipal.IsInRole("Manager") Then 
   ' enable manager-level features 
 End If 
  Else 
 ' deny access 
  End If

Dieser Code setzt voraus, dass wir den System.Threading-Namespace importiert haben, um leichter Zugang zu der Threadklasse zu erhalten. Zuerst wird geprüft, ob der aktuelle Benutzer erfolgreich authentifiziert wurde. Wenn dies der Fall ist, fahren wir mit der Autorisierung fort, anderenfalls verweigern wir den Zugriff auf die Anwendung. In einigen Fällen können wir, statt den Zugriff vollständig zu verweigern, einen "Gastzugang" auf niedrigerer Ebene für nicht authentifizierte Benutzer bereitstellen.

Wenn der Benutzer authentifiziert ist, zeigen wir den Benutzernamen in einem Label-Steuerelement an und prüfen, ob der Benutzer zur "Manager"-Rolle gehört. Wir können auf diese Weise auf Grundlage der Rolle des Benutzers Features der Anwendung auswählen und aktivieren oder deaktivieren. Die IsInRole-Methode können wir beim Starten der Anwendung oder an jeder beliebigen Stelle im Code, an der Geschäftsverhalten von der Benutzerrolle abhängt, verwenden.

Verwenden des "Principal"-Objekts von "HttpContext"

In einer Web Forms-Anwendung würden wir ähnlichen Code schreiben:

  If HttpContext.Current.User.Identity.IsAuthenticated Then 
 lblUserName.Text = HttpContext.Current.User.Identity.Name 
 If HttpContext.Current.User.IsInRole("Manager") Then 
   ' enable manager-level features 
 End If 
  Else 
 ' deny access 
  End If

Im ersten Beispiel wird Thread.CurrentPrincipal verwendet. Es stellt sowohl für Windows Forms als auch für Web Forms gültigen Code dar. Im zweiten Beispiel wird HttpContext.Current.User verwendet. Dieser Code ist nur in ASP.NET-Anwendungen mit vorhandenem HttpContext gültig. Im Allgemeinen sollte Code in einer Klassenbibliothek mit Thread.CurrentPrincipal auf das aktuelle Principal-Objekt zugreifen und nicht versuchen, HttpContext.Current.User zu verwenden. Beim Ausführen in einer Webanwendung hat Thread.CurrentPrincipal standardmäßig denselben Wert wie HttpContext.Current.User.

Benutzerauthentifizierung

Der Code, den wir bis jetzt erläutert haben, setzt ein gültiges Principal-Objekt voraus, das mit dem Thread oder HttpContext verbunden ist. Wie entsteht nun dieses Principal-Objekt?

Es ist wichtig zu wissen, dass Microsoft .NET Framework für jeden Thread ein Principal-Objekt bereitstellt. Regeln bestimmen, wie (oder ob) dieses Principal-Standardobjekt erstellt wird.

Der Ereignisablauf ist davon abhängig, ob wir eine ASP.NET-Anwendung oder eine Anwendung, die nicht für das Web bestimmt ist, erstellen. Wir werden beide Szenarios erläutern.

Anwendungen außerhalb von ASP.NET

Wenn der Thread nicht in ASP.NET, sondern zum Beispiel in einer Windows Forms- oder Konsolenanwendung ausgeführt wird, verfügt er über ein Principal-Standardobjekt vom Typ GenericPrincipal, das im Grunde leer ist. Jeder Aufruf von IsInRole wird mit False zurückgegeben, da das Principal-Objekt keine gültigen Rollen hat.

Das Standard-GenericPrincipal hat ein Identity-Standardobjekt vom Typ GenericIdentity. Dieses Objekt ist im Wesentlichen auch leer. Beim Aufruf von IsAuthenticated gibt es False zurück und gibt sowohl für AuthenticationType als auch für Name eine leere Zeichenfolge zurück.

Die "SetPrincipalPolicy"-Methode

Wir können das Standardverhalten des Thread-Principal-Objekts steuern. Hierfür verwenden wir die SetPrincipalPolicy-Methode der aktuellen Anwendungsdomäne. Dies ist der Standard:

 AppDomain.CurrentDomain.SetPrincipalPolicy( _ 
   PrincipalPolicy.UnauthenticatedPrincipal)

Wir haben folgende Optionen:

Parameter

Beschreibung

PrincipalPolicy.NoPrincipal

Stellt kein Principal-Standardobjekt für den Thread bereit. Das bedeutet, dass die CurrentPrincipal-Methode "Nothing" zurückgibt, solange wir nicht manuell ein gültiges Principal-Objekt einrichten.

PrincipalPolicy.UnauthenticatedPrincipal

(Standard) Es wird ein nicht authentifiziertes GenericPrincipal mit einem nicht authentifizierten GenericIdentity mit dem Thread verbunden.

PrincipalPolicy.WindowsPrincipal

Es werden WindowsPrincipal- und WindowsIdentity-Objekte, die der Identität des aktuellen Windows-Benutzers entsprechen, mit dem Thread verbunden.

Die SetPrincipalPolicy-Methode wird normalerweise beim Starten der Anwendung aufgerufen. Auf Basis der Richtlinie wird ggf. das entsprechende Principal-Standardobjekt erstellt.

Aus Leistungsgründen wird kein Principal-Standardobjekt erstellt, bis wir das erste Mal auf die CurrentPrincipal-Methode des Thread zugreifen und somit das Principal-Objekt anfordern. Einige Principal-Objekte, insbesondere WindowsPrincipal, können bei ihrer Erstellung viel Leistung in Anspruch nehmen. .NET Framework erstellt das Objekt daher nur, wenn wir darauf zugreifen.

Wenn wir unsere eigene benutzerdefinierte Authentifizierung implementieren, entscheiden wir, wie und wann die entsprechenden Principal- und Identity-Objekte erstellt werden. Darauf kommen wir später in diesem Artikel noch zurück.

Lassen Sie uns die Gründe für die Verwendung der einzelnen Richtlinien betrachten.

"NoPrincipal"

NoPrincipal ist beim Implementieren von benutzerdefinierter Authentifizierung eine sinnvolle Option. Hauptzweck der Option NoPrincipal ist es, sicherzustellen, dass jedem Thread ein gültiges Principal-Objekt zugeordnet wird. Wenn wir als Richtlinie NoPrincipal wählen, wissen wir, dass Threads kein Principal-Standardobjekt erhalten. Sie erhalten eines, das wir zuordnen, oder überhaupt keines.

Dies trägt mit dazu bei, sicherzustellen, dass kein Benutzer ohne Authentifizierung in das System gelangt. Wir können die Option NoPrincipal verwenden, wenn wir für die Anwendung keine Gäste und keinen anonymen Zugriff zulassen möchten.

"UnauthenticatedPrincipal"

Die Standardoption UnauthenticatedPrincipal ist beim Implementieren von benutzerdefinierter Authentifizierung sinnvoll. Jeder Thread erhält ein gültiges, aber nicht authentifiziertes Principal-Objekt. Es ist außerdem die einfachste Option, da wir den Autorisierungscode schreiben können, ohne uns darum zu kümmern, dass das CurrentPrincipal-Objekt "Nothing" sein könnte.

Beim Implementieren von benutzerdefinierter Authentifizierung ordnen wir einem Thread ausdrücklich ein authentifiziertes Principal-Objekt zu und ersetzen damit das nicht authentifizierte Principal-Standardobjekt.

"WindowsPrincipal"

Die Option WindowsPrincipal ist sinnvoll, wenn wir die integrierte Windows- oder Domänensicherheit verwenden möchten. Bei dieser Option erstellt das .NET Framework ein WindowsPrincipal- und ein dazugehöriges WindowsIdentity-Objekt, das der Identität des Benutzers für den aktuellen Vorgang entspricht, wenn zum ersten Mal ein Principal-Objekt von einem beliebigen Thread angefordert wird.

Bei einer Windows Forms-Anwendung handelt es sich um den am Computer angemeldeten Benutzer. Bei einem Webdienst handelt es sich um das Benutzerkonto, für das der Dienst ausgeführt wird.

Das WindowsPrincipal-Objekt verwendet die NT-Gruppen des Benutzers als Rollen für die IsInRole-Methode. Wir können daher den folgenden Code verwenden, um festzustellen, ob der Benutzer der Power Users-NT-Gruppe (Hauptbenutzer) oder der Accounting-Gruppe (Domänenkonto) angehört:

 AppDomain.CurrentDomain.SetPrincipalPolicy( _ 
   PrincipalPolicy.WindowsPrincipal) 
 If Thread.CurrentPrincipal.IsInRole("BUILTIN\Power Users") 
   ' they are a power user 
 End If 
 If Thread.CurrentPrincipal.IsInRole("MYDOMAIN\Accounting") 
   ' they are in accounting 
 End If

Um die genaue NT-Gruppe zu bestimmen, sind der lokale Computername oder Domänenname erforderlich. Für integrierte, in Windows vordefinierte Gruppen verwenden wir die spezielle BUILTIN-"Domäne".

Nachdem wir beschrieben haben, wie SetPrincipalPolicy das Principal-Standardobjekt für einen Thread bestimmt und wie die Windows-Authentifizierung funktioniert, können wir jetzt zum Implementieren der benutzerdefinierten Authentifizierung kommen.

Implementieren benutzerdefinierter Authentifizierung

Die benutzerdefinierte Authentifizierung ist wichtig, da die integrierte Windows-Sicherheit nicht für alle Umgebungen geeignet ist. In vielen Fällen sind die Benutzer nicht alle Teil derselben Domäne, oder bei der Authentifizierungsdatenbank des Unternehmens handelt es sich nicht um eine Windows-Datenbank oder ein Active Directory. Die Authentifizierungsdaten der Benutzer, mit denen wir die Anmeldeinformationen abgleichen möchten, können in einer Datenbank, auf einem LDAP-Server (Lightweight Directory Access Protocol) oder einem anderen Standort gespeichert sein.

Wir können die benutzerdefinierte Authentifizierung implementieren, wenn als Principal-Richtlinie entweder UnauthenticatedPrincipal oder NoPrincipal eingerichtet ist. In beiden Fällen können wir Code implementieren, um die Anmeldeinformationen des Benutzers zu erfassen, ihre Gültigkeit anhand der Authentifizierungsdatenbank zu prüfen und unser eigenes Principal- und Identity-Objekt für den Benutzer auszugeben. Dieses benutzerdefinierte Principal- und Identity-Objekt kann dem Thread zugeordnet werden und sogar als neue Standardidentität für alle neuen Threads in der Anwendungsdomäne festgelegt werden.

Die benutzerdefinierten Principal- und Identity-Objekte können spezifische Instanzen von GenericPrincipal und GenericIdentity sein, oder wir können eigene benutzerdefinierte Klassen erstellen. In den meisten Fällen stellt die GenericPrincipal-Klasse genügend Funktionen für unsere Bedürfnisse bereit.

Eigene benutzerdefinierte Principal- und Identity-Klassen erstellen wir, wenn wir weitere Informationen oder Funktionen bereitstellen möchten, die über das hinausgehen, was durch die IPrincipal- und IIdentity-Schnittstellen definiert ist. Ein Beispiel: Wir möchten mehr als nur den Benutzernamen mithilfe der Name-Eigenschaft des Identity-Objekts bereitstellen. In diesem Fall könnten wir eine benutzerdefinierte Identity-Klasse erstellen, die die E-Mail-Adresse, Abteilungsnummer oder andere nützliche Informationen über den Benutzer offen legt.

Da GenericPrincipal und GenericIdentity normalerweise ausreichen, verwenden wir in diesem Artikel diese Klassen bei der Implementierung des benutzerdefinierten Authentifizierungscodes.

Für die benutzerdefinierte Authentifizierung müssen wir eine Login-Methode erstellen, die die Anmeldeinformationen des Benutzers als Parameter akzeptiert. Wir verwenden anschließend diese Anmeldeinformationen, um den Benutzer zu authentifizieren. Wenn der Benutzer gültig ist, erstellen wir für ihn ein entsprechendes Principal- und Identity-Objekt. Anderenfalls stellen wir sicher, dass der Thread ein nicht authentifiziertes Principal- und Identity-Objektpaar erhält:

  Public Shared Sub Login( _ 
 ByVal username As String, ByVal password As String) 
 Dim principal As GenericPrincipal 
 ' make sure we're set for custom authentication 
 AppDomain.CurrentDomain.SetPrincipalPolicy( _ 
   PrincipalPolicy.UnauthenticatedPrincipal) 
 Dim valid As Boolean 
 ' find out if the credentials are valid 
 If username = "rocky" AndAlso password = "lhotka" Then 
   valid = True 
 Else 
   valid = False 
 End If 
 If valid Then 
   ' load the user's roles 
   Dim roles() As String = {"Authors", "Speakers"} 
   ' create the Principal and Identity objects 
   Dim identity As New GenericIdentity(username, "Custom") 
   principal = New GenericPrincipal(identity, roles) 
 Else 
   ' the credentials were not valid 
   ' so create an unauthenticated Principal/Identity 
   Dim identity As New GenericIdentity("", "") 
   principal = New GenericPrincipal(identity, New String() {}) 
 End If 
 ' set the thread's current principal 
 Thread.CurrentPrincipal = principal 
  End Sub

Um den Code für Ihre eigene Verwendung anzupassen, ändern Sie einfach den Code, der Benutzernamen und Kennwort überprüft, so dass die Anmeldeinformationen anhand Ihrer Datenbanktabelle oder Ihres LDAP-Servers oder anderen Listen mit Anmeldeinformationen von Benutzern geprüft werden. Ändern Sie anschließend den Code, der die Liste mit Rollen lädt, damit die Benutzerrollen von Ihrer eigenen Datenquelle geladen werden.

Ihre Anwendung kann jetzt die Anmeldeinformationen des Benutzers durch ein Anmeldeformular oder einen anderen Mechanismus sammeln und anschließend die Login-Methode aufrufen. Der Code hierfür kann in etwa so aussehen:

  Login(txtUsername.Text, txtPassword.Text) 
  If Not Thread.CurrentPrincipal.Identity.IsAuthenticated Then 
 MsgBox("Incorrect username/password") 
  End If

Wir können auch eine SignOut-Methode erstellen, die aufgerufen wird, um den Benutzer von der Anwendung abzumelden:

  Public Shared Sub SignOut() 
 Dim identity As New GenericIdentity("", "") 
 Dim principal As New GenericPrincipal(identity, New String() {}) 
 ' set the thread's current principal 
 Thread.CurrentPrincipal = principal 
  End Sub

Wenn diese Methode aufgerufen wird, wird als Principal-Objekt des Threads ein nicht authentifiziertes Principal- und Identity-Objektpaar festgelegt.

Festlegen des "Principal"-Objekts für Hintergrundthreads

Die Login- und SignOut-Methoden bilden die Grundlage, damit Benutzer sich bei einer Windows-Anwendung an- und abmelden können. Für reguläre Single-Thread-Anwendungen benötigen wir nichts anderes. Beim Erstellen einer Multithreading-Anwendung können wir uns aber etwas mehr Arbeit machen.

Immer wenn ein neuer Thread erstellt wird, wird sein Principal-Standardobjekt von der Principal-Richtlinie der Anwendungsdomäne bestimmt, wie weiter vorne im Artikel beschrieben wurde. Bei benutzerdefinierter Sicherheit lautet die Principal-Richtlinie UnAuthenticatedPrincipal. Jeder neu erstellte Thread in der Anwendungsdomäne erhält ein nicht authentifiziertes GenericPrincipal-Standardobjekt.

Dies kann unerwünscht sein, wenn die neuen Hintergrundthreads das gleiche authentifizierte Principal-Objekt erhalten sollen wie der Hauptthread der Anwendungsdomäne.

Sie können dieses Problem lösen, indem Sie sicherstellen, dass jeder Thread bei seiner Erstellung mit dem Principal-Objekt des aktuellen Threads initialisiert wird:

 Dim t As New Thread(AddressOf DoWork) 
 t.CurrentPrincipal = Thread.CurrentPrincipal 
 t.Start()

Dieser Ansatz funktioniert gut für Threads, die wir ausdrücklich erstellen. Für Threads, die im Threadpool der Anwendungsdomäne erstellt werden, ist er nicht geeignet. Immer, wenn wir den BeginInvoke-Aufruf für einen Delegaten oder eine andere Methode verwenden, nutzen wir den Threadpool. Das Gleiche gilt für andere BeginXYZ-Objektmethoden im .NET Framework. Wir können auch ThreadPool.QueueUserWorkItem aufrufen und so ausdrücklich den Threadpool verwenden.

Da die Threads im Threadpool automatisch erstellt werden, können wir ihr Principal-Objekt nicht festlegen. Wir haben in diesem Fall zwei Möglichkeiten.

Die erste Möglichkeit: Unser Code, der auf dem Hintergrundthread ausgeführt wird, legt den Thread.CurrentPrincipal-Wert fest. Aufgrund der Fehleranfälligkeit ist dies kein idealer Lösungsweg. Wir müssen für diese Aufgabe jeder Arbeitsmethode manuell Code hinzufügen, und dies ist nicht immer möglich. Wenn wir zum Beispiel BeginConnect für ein TCP-Socketobjekt aufrufen, können wir die Arbeitsweise des Socketobjekts nicht ändern, und das Principal-Objekt würde folglich nicht festgelegt.

Der zweite, bessere Ansatz: Wir können das Principal-Standardobjekt für die gesamte Anwendungsdomäne festlegen. Jeder Thread, der innerhalb der Anwendungsdomäne erstellt wird, verwendet dann automatisch dieses Principal-Objekt. Das gilt für ausdrücklich erstellte Threads und die im Threadpool.

Der Nachteil bei diesem Ansatz ist, dass das Principal-Standardobjekt der Anwendungsdomäne nur einmal festgelegt werden kann. Wenn der Benutzer sich bei der Anwendung an- und abmelden kann, ohne die Anwendung zu schließen, macht es womöglich wenig oder keinen Sinn, das Principal-Standardobjekt für die Anwendungsdomäne festzulegen.

Mit dem folgenden Code legen wir das Principal-Standardobjekt für die Anwendungsdomäne fest:

 AppDomain.CurrentDomain.SetThreadPrincipal(Thread.CurrentPrincipal)

Normalerweise führen wir diesen Aufruf aus, sobald sichergestellt ist, dass der Benutzer korrekt authentifiziert wurde, wie in diesem Beispiel:

  Login(txtUsername.Text, txtPassword.Text) 
  If Not Thread.CurrentPrincipal.Identity.IsAuthenticated Then 
 MsgBox("Invalid username/password") 
  Else 
 AppDomain.CurrentDomain.SetThreadPrincipal(Thread.CurrentPrincipal) 
  End If

Jeder Versuch, SetThreadPrincipal mehr als einmal während der Lebensdauer der Anwendungsdomäne aufzurufen, löst eine Ausnahme aus.

Wenn SetThreadPrincipal aufgerufen wurde, werden alle neu erstellten Threads automatisch dem Principal-Objekt, das wir angegeben haben, zugeordnet. Auf diese Weise wird das Principal-Objekt den Threads im Threadpool zugeordnet.

Wir haben jetzt eine gute Vorstellung davon, wie benutzerdefinierte Sicherheit in einer Windows-Anwendung implementiert wird. Betrachten wir nun die Vorgehensweise für Webanwendungen in ASP.NET.

Code in ASP.NET

Wenn der Code innerhalb von ASP.NET ausgeführt wird, zum Beispiel in Web Forms oder Webdiensten, geben ASP.NET und IIS vor, wie Thread und HttpContext ihre Principal-Standardobjekte erhalten. Die SetPrincipalPolicy-Methode aus dem vorigen Abschnitt wird in ASP.NET-Anwendungen normalerweise nicht verwendet.

Es gibt drei Hauptszenarios für die Authentifizierung in ASP.NET: die Windows-basierte, die formularbasierte oder die externe Authentifizierung.

Windows-Authentifizierung

Die Windows-Authentifizierung basiert auf der Windows-Identität des Benutzers. Diese Identität kann entweder mithilfe von Pass-Through-Security direkt vom Client an den Webserver übergeben werden, oder aber der Webserver verwendet einen Browser, um ein Dialogfeld zur Anmeldung anzuzeigen, in dem die Benutzer Benutzernamen und Kennwort für den Server eingeben können.

In beiden Fällen ist die ASP.NET-Umgebung eingerichtet, wenn der Code ausgeführt wird. Unser Code wird daher unter der Benutzeridentität ausgeführt. Sowohl HttpContext als auch der Thread enthalten in diesem Fall Verweise auf die WindowsPrincipal- und WindowsIdentity-Objekte des Benutzers.

In der Nicht-ASP.NET-Umgebung haben wir die Verwendung von Windows-Authentifizierung mithilfe der SetPrincipalPolicy-Methode festgelegt. In der ASP.NET-Umgebung ermöglichen wir die Windows-Authentifizierung durch die Änderung der Sicherheitsoptionen für das virtuelle Stammverzeichnis der Website.

In der IIS Management Console deaktivieren wir den anonymen Zugriff auf die Website und aktivieren die integrierte Windows-Authentifizierung, wie in Abbildung 3 dargestellt.

AuthentifizierungundAutorisierung_03.gif

Abbildung 3. Aktivieren der integrierten Windows-Authentifizierung

Wir müssen uns außerdem vergewissern, dass die web.config-Datei den Windows-Standardwert für die Authentifizierung enthält:

 <authentication mode="Windows" />

Zu diesem Zeitpunkt enthalten die Werte von HttpContext.Current.User und Thread.CurrentPrincipal beide automatisch Verweise auf das entsprechende WindowsPrincipal-Objekt. Wir können anschließend dieses Objekt im Anwendungscode für die Autorisierung verwenden:

  If HttpContext.Current.User.IsAuthenticated Then 
 lblUserName.Text = HttpContext.Current.User.Identity.Name 
 If HttpContext.Current.User.IsInRole("Manager") Then 
   ' enable manager-level features 
 End If 
  Else 
 ' deny access 
  End If

Dies ist im Wesentlichen derselbe Code, den wir für die Autorisierung in Windows-Anwendungen verwendet haben. In ASP.NET-Code greifen wir jedoch eher durch HttpContext als durch den aktuellen Thread auf das Principal-Objekt zu.

Formularbasierte Authentifizierung

Die Windows-Authentifizierung ist einfach anzuwenden, aber für Webanwendungen oft ungeeignet. Die meisten Benutzer von Webdienstanwendungen haben kein Konto innerhalb unserer Domäne oder im Active Directory. Daher müssen wir ein benutzerdefiniertes Sicherheitsschema implementieren.

Die in ASP.NET integrierte formularbasierte Authentifizierungsfunktion ist eine praktische Lösung. Wie wir im Folgenden aufzeigen werden, erledigt diese Lösung allerdings nicht automatisch alle Detailaufgaben.

Für die formularbasierte Authentifizierung muss der IIS den anonymen Zugriff auf die Website zulassen. Dies ist die Standardeinstellung für ein neues virtuelles Stammverzeichnis, die in der IIS Management Console geändert werden kann.

AuthentifizierungundAutorisierung_04.gif

Abbildung 4. Aktivieren des anonymen Zugriffs

Als Nächstes konfigurieren wir die Anwendung für die Verwendung formularbasierter Sicherheit. Hierfür ändern wir in der web.config-Datei sowohl das <authentication>- als auch das <authorization>-Element:

   <authentication mode="Forms"> 
   <forms name="login"  
 loginUrl="login.aspx" protection="All" timeout="60" /> 
   </authentication> 
 <authorization> 
  <deny users="?" /> <!-- Block unauthorized users --> 
 </authorization>

Im <forms>-Element spezifizieren wir ein loginUrl-Attribut, das auf eine bestimmte Webseite in unserer Anwendung verweist. Diese Webseite hat die Aufgabe, den Benutzer zur Eingabe seiner Anmeldeinformationen aufzufordern und ihn auf Basis dieser Anmeldeinformationen zu authentifizieren. Bis der Benutzer authentifiziert ist, leitet ASP.NET ihn bei jedem Zugriff auf die Website automatisch zu dieser Anmeldeseite.

Wir müssen daher login.aspx implementieren. Die Seite muss den Benutzer zur Eingabe von Anmeldeinformationen auffordern, normalerweise Benutzername und Kennwort. Die Seite verwendet dann diese Informationen, um den Benutzer zu authentifizieren. Die Datenübertragung vom Browser zum Webserver erfolgt in Klartext. Verwenden Sie SSL (Secure Sockets Layer), um die Seite zu schützen und das Benutzerkennwort vor der Übertragung vom Browser zum Server zu verschlüsseln.

Um den Benutzer zu authentifizieren, können wir eine Login-Methode ähnlich der Methode, die wir für Windows-Anwendungen erstellt haben, implementieren:

  Public Shared Sub Login( _ 
 ByVal username As String, ByVal password As String) 
 Dim principal As GenericPrincipal 
 Dim valid As Boolean 
 ' find out if the credentials are valid 
 If username = "rocky" AndAlso password = "lhotka" Then 
   valid = True 
 Else 
   valid = False 
 End If 
 If valid Then 
   ' load the user's roles 
   Dim roles() As String 
   ' create the Principal and Identity objects 
   Dim identity As New GenericIdentity(username, "Custom") 
   principal = New GenericPrincipal(identity, roles) 
 Else 
   ' the credentials were not valid 
   ' so create an unauthenticated Principal/Identity 
   Dim identity As New GenericIdentity("", "") 
   principal = New GenericPrincipal(identity, New String() {}) 
 End If 
 ' set the current principal 
 HttpContext.Current.User = principal 
  End Sub

Zu der vorigen Implementierung gibt es zwei Unterschiede. Der erste Unterschied besteht darin, dass wir keine Principal-Richtlinie für die Anwendungsdomäne festlegen. Diese wird automatisch durch unsere web.config-Einstellungen bestimmt. Zweitens legen wir HttpContext.Current.User anstelle von Thread.CurrentPrincipal fest. Dadurch wird das Principal-Objekt für ASP.NET-Code verfügbar. Außerdem wird Thread.CurrentPrincipal automatisch festgelegt, sodass jeder Klassenbibliothekscode, den wir verwenden, ebenfalls Zugang zu diesem Principal-Objekt hat.

Wir können an dieser Stelle HttpContext.Current.User verwenden, um zu bestimmen, ob der Benutzer authentifiziert wurde, und ihm in diesem Fall den Zugriff auf die Website zu erlauben.

Es gilt aber eine weitere Hürde zu überwinden. Das Web an sich ist statusfrei, unsere Principal- und Identity-Objekte haben einen Status. Wir müssen also dafür sorgen, dass diese Objekte nicht verloren gehen, sobald der Benutzer die login.aspx-Seite verlässt. Das Principal-Objekt muss für alle Seiten der Webanwendung permanent verfügbar sein, da es der Mechanismus ist, den wir zum Implementieren der Autorisierungslogik verwenden.

Wir können den Status des Principal-Objekts wie jeden anderen Benutzerstatus von Seite zu Seite speichern. Die wesentlichen Optionen dabei sind die Speicherung der Daten in einer temporären Datenbanktabelle, im ASP.NET-Sitzungsobjekt oder mithilfe eines Cookies im Browser. Welche Option wir auch wählen, wir müssen sicherstellen, dass das Principal-Objekt vor dem Laden einer Seite in der Anwendung in HttpContext.Current.User festgelegt ist.

Die einfachste Form, das Principal-Objekt zu speichern, ist ein Cookie auf dem Client. Die formularbasierte Sicherheit verwaltet bereits ein verschlüsseltes Cookie für uns, und sie stellt die Infrastruktur zur Verfügung, mit der wir dem Cookie weitere Daten hinzufügen können. Wir erstellen nun eine Methode, die das Sicherheitscookie richtig konfiguriert und den Benutzer wieder zu der Seite führt, die er ursprünglich angefordert hatte, bevor er auf login.aspx umgeleitet wurde.

  Private Sub RedirectFromLogin() 
 ' serialize the Principal into a String value 
 Dim principalText As String 
 Dim buffer As New IO.MemoryStream 
 Dim formatter As _ 
   New Runtime.Serialization.Formatters.Binary.BinaryFormatter 
 formatter.Serialize(buffer, HttpContext.Current.User) 
 buffer.Position = 0 
 principalText = Convert.ToBase64String(buffer.GetBuffer) 
 ' create the ticket 
 Dim ticket As New FormsAuthenticationTicket( _ 
   1, HttpContext.Current.User.Identity.Name, _ 
   Now, DateAdd(DateInterval.Minute, 20, Now), _ 
   False, principalText) 
 ' Encrypt the ticket. 
 Dim encTicket As String = FormsAuthentication.Encrypt(ticket) 
 ' Create the cookie. 
 Response.Cookies.Add( _ 
   New HttpCookie(FormsAuthentication.FormsCookieName, _ 
   encTicket)) 
 ' Redirect back to original URL. 
 Response.Redirect( _ 
   FormsAuthentication.GetRedirectUrl(txtUsername.Text, False)) 
  End Sub

Zunächst einmal serialisieren wir die Principal- und Identity-Objekte in ein MemoryStream. Anschließend konvertieren wir diese Daten in druckbaren Text, um die Daten des Principal-Objekts in das Sicherheitscookie laden zu können.

Der nächste Schritt ist das Erstellen des formularbasierten Sicherheitstickets. Dieses Ticket enthält die für die formularbasierte Sicherheit erforderlichen Daten. Optional kann es auch benutzerdefinierte Daten, die wir zur Verfügung stellen, enthalten. In unserem Fall handelt es sich dabei um die serialisierten Daten des Principal-Objekts. Wir stellen aber nicht nur die Daten des Principal-Objekts zur Verfügung, wir legen auch Datum/Uhrzeit für die Ausstellung des Tickets und Datum/Uhrzeit für seine Gültigkeit fest.

Wenn das Ticket erstellt wurde, müssen wir es verschlüsseln, um eine Manipulation auf dem Client zu verhindern. Da das Ticket in einem Cookie auf dem Clientcomputer gespeichert wird, besteht immer die Möglichkeit, dass ein böswilliger Benutzer versuchen könnte, auf das Ticket zuzugreifen oder es zu manipulieren. Kennwortdaten sind in den Principal- und Identity-Objekten sowie dem Ticket nicht enthalten.

Wir erstellen zum Schluss mithilfe der verschlüsselten Ticketdaten ein Cookie und fügen es der enthaltenen Auflistung von Cookies als Ausgabe dieser Seite hinzu.

An diesem Punkt sind alle Sicherheitsobjekte konfiguriert. Wir können den Benutzer durch Response.Redirect auf die Seite zurückführen, die er ursprünglich angefordert hatte. Wir verwenden Response.Redirect anstelle von FormsAuthentication.RedirectFromLoginPage, da wir das Sicherheitscookie angepasst haben.

Wenn diese Methode vollständig ist, können wir Code für die Login-Schaltfläche auf login.aspx schreiben, um die beiden Methoden Login und RedirectFromLogin aufzurufen:

  Private Sub btnLogin_Click( _ 
 ByVal sender As System.Object, _ 
 ByVal e As System.EventArgs) Handles btnLogin.Click 
 Login(txtUsername.Text, txtPassword.Text) 
 If HttpContext.Current.User.Identity.IsAuthenticated Then 
   RedirectFromLogin() 
 End If 
  End Sub

Wir nehmen die Anmeldeinformationen des Benutzers, rufen die Login-Methode auf, und überprüfen, ob der Benutzer authentifiziert wurde. Wenn dies der Fall ist, rufen wir die RedirectFromLogin-Methode auf, um das Sicherheitscookie zu erstellen und den Benutzer wieder zu seinem Ziel zu führen. Ist der Benutzer nicht authentifiziert, bleibt er automatisch auf der login.aspx-Seite.

An dieser Stelle sind wir schon fast fertig. Wir müssen lediglich noch sicherstellen, dass das Principal-Objekt wieder vom Cookie abgerufen und in HttpContext.Current.User festgelegt wird, bevor Seiten innerhalb der Anwendung ausgeführt werden.

Zu diesem Zweck fügen wir Global.asax Code hinzu, um das globale AcquireRequestState-Ereignis zu verarbeiten. Dieses Ereignis wird vor dem Ausführen von Seiten ausgelöst. Es ist der ideale Ort, um den HttpContext-Benutzerwert festzulegen.

  Private Sub Global_AcquireRequestState(ByVal sender As Object, _ 
 ByVal e As System.EventArgs) Handles MyBase.AcquireRequestState 
 ' get the security cookie 
 Dim cookie As HttpCookie = _ 
   Request.Cookies.Get(FormsAuthentication.FormsCookieName) 
 If Not cookie Is Nothing Then 
   ' we got the cookie, so decrypt the value 
   Dim ticket As FormsAuthenticationTicket = _ 
  FormsAuthentication.Decrypt(cookie.Value) 
   If ticket.Expired Then 
  ' the ticket has expired - force user to login 
  FormsAuthentication.SignOut() 
  Response.Redirect("login.aspx") 
   Else 
  ' ticket is valid, set HttpContext user value 
  Dim buffer As _ 
 New IO.MemoryStream(Convert.FromBase64String(ticket.UserData)) 
  Dim formatter As _ 
 New Runtime.Serialization.Formatters.Binary.BinaryFormatter 
  HttpContext.Current.User = _ 
 CType(formatter.Deserialize(buffer), IPrincipal) 
   End If 
 End If 
  End Sub

Als Erstes rufen wir das Sicherheitscookie vom Request-Objekt ab. Wenn wir das Cookie erfolgreich erhalten, entschlüsseln wir das Ticket und überprüfen seine Gültigkeit. Wenn wir das Cookie nicht erhalten, wird der Benutzer von ASP.NET automatisch auf die Anmeldeseite weitergeleitet. Falls das Ticket abgelaufen ist, leiten wir den Benutzer manuell auf die Anmeldeseite weiter.

Ein gültiges Ticket vorausgesetzt, rufen wir den Zeichenfolgenwert ab, der das serialisierte Principal-Objekt enthält. Der Zeichenfolgenwert wird in seine binäre Form zurückversetzt und verwendet, um ein MemoryStream-Objekt zu initialisieren. Diese Daten werden dann deserialisiert, um erneut die Principal- und Identity-Objekte zu erstellen, die in HttpContext geladen werden.

Anschließend ist der Sicherheitskontext für den Seitencode bereit. Wir können HttpContext.Current.User und Thread.CurrentPrincipal verwenden, um für die Autorisierungslogik auf das Principal-Objekt zuzugreifen.

Externe Authentifizierung

Windows-Authentifizierung und benutzerdefinierte Authentifizierung werden zwar am meisten verwendet, aber einige Unternehmen verwenden Sicherheitstools von Fremdanbietern wie Oblix und SiteMinder, um Einzelanmeldungen für ihre gesamten Websites bereitzustellen.

Diese Tools leiten nicht authentifizierte Benutzer zur Authentifizierung auf einen Anmeldeserver. Bis zu ihrer Authentifizierung durch den Sicherheitsmechanismus des Fremdanbieters haben Benutzer keinen Zugriff auf den Websitecode. Informationen darüber, wie Sie IIS und ASP.NET für eine reibungslose Zusammenarbeit mit dem Produkt konfigurieren, finden Sie in der jeweiligen Produktdokumentation des Fremdanbieters.

Beim Einsatz eines externen Fremdanbieterprodukts wird die Authentifizierung weder durch den IIS noch durch ASP.NET ausgeführt; wir brauchen diesen Aspekt daher nicht zu beachten. Wir müssen aber trotzdem ein gültiges Principal-Objekt für den Benutzer erhalten, um Autorisierungscode für die Anwendung schreiben zu können.

Die Fremdanbieterprodukte enthalten normalerweise zusätzliche Informationen im HTTP-Header, sobald der Benutzer authentifiziert wurde. Zum Beispiel enthalten sie oft den Wert des Benutzernamens als benutzerdefinierten HTTP-Header-Wert auf jeder Seitenanforderung. Wir können diesen Wert verwenden, um die Rollen- und Identitätsinformationen des Benutzers zu laden.

Denken Sie daran, dass wir den Benutzer nicht authentifizieren müssen. Das Fremdanbieterprodukt hat dies bereits erledigt. Wir müssen lediglich die bereits authentifizierten Benutzerprofildaten laden, um ein Principal- und Identity-Objekt zu erstellen. Vorausgesetzt, dass wir die Benutzerdaten auf Basis der Benutzernamen laden können, können wir in etwa folgenden Code zu Global.asax hinzufügen:

  Private Sub Global_AcquireRequestState(ByVal sender As Object, _ 
 ByVal e As System.EventArgs) Handles MyBase.AcquireRequestState 
 Dim principal As GenericPrincipal 
 Dim username As String 
 ' get the username from the HTTP header 
 username = Request.Headers.Get("USERNAME") 
 If Len(username) > 0 Then 
   ' load the user's roles 
   Dim roles() As String 
   ' create the Principal and Identity objects 
   Dim identity As New GenericIdentity(username, "Custom") 
   principal = New GenericPrincipal(identity, roles) 
   ' set the current principal 
   HttpContext.Current.User = principal 
 End If 
  End Sub

Wir verwenden erneut das AcquireRequestState-Ereignis, um die Sicherheit einzurichten, bevor Seitencode ausgeführt wird. Wir rufen in diesem Fall den Wert des Benutzernamens aus dem HTTP-Header ab. Sie müssen den Namen des Headers entsprechend dem Wert anpassen, den das verwendete Fremdanbieter-Sicherheitsprodukt bereitstellt.

Wenn wir den Wert des Benutzernamens erhalten, laden wir mit ihm die Rollenliste des Benutzers. Wie bei unseren vorangegangenen Beispielen müssen Sie diesen Code anpassen, damit die Werte aus Ihrer Datenbank, einem LDAP-Server oder anderen Speichermöglichkeiten für das Benutzerprofil gelesen werden.

Die Benutzerprofildaten werden anschließend verwendet, um die GenericIdentity- und GenericPrincipal-Objekte zu erstellen, und der HttpContext.Current.User-Wert wird auf das neue Principal-Objekt gesetzt. Wenn die Seite den Code jetzt ausführt, besteht Zugang zum Sicherheitskontext des Benutzers.

Schlussfolgerung

Das Microsoft .NET Framework enthält flexible Authentifizierungs- und Autorisierungsfunktionen, um Sicherheit in Anwendungen zu implementieren. Wir können für die Anwendungen zwischen Windows- und benutzerdefinierten Sicherheitsoptionen wählen. Der Anwendungscode zum Ausführen der Autorisierung bleibt in beiden Fällen gleich und vertraut auf die grundlegenden, in der Basisklassenbibliothek integrierten Funktionen.

Ob Sie nun Windows- oder Webanwendungen erstellen, das Wissen über Principal- und Identity-Objekte wird Ihnen dabei sehr nützlich sein.


Microsoft führt eine Onlineumfrage durch, um Ihre Meinung zur MSDN-Website zu erfahren. Wenn Sie sich zur Teilnahme entscheiden, wird Ihnen die Onlineumfrage angezeigt, sobald Sie die MSDN-Website verlassen.

Möchten Sie an der Umfrage teilnehmen?
Anzeigen:
© 2014 Microsoft