Peter Bucher - Mein Experiment, meine Spielereien, meine Welt...   ·   Stefan Falz   ·   Jürgen Gutsch   ·   Golo Roden   ·   ASP.NET Zone   ·   Microsoft ASP.NET
Willkommen bei ASP.NET Zone. Anmelden | Registrieren | Hilfe

Basic Authentication mit ASP.NET

Möglichkeiten der Authentifizierung mit ASP.NET

Mit ASP.NET gibt es verschiedene Möglichkeiten, sich am Server anzumelden, die bekannteste davon ist die Möglichkeit über Membership-API von ASP.NET.

Zudem gibt es noch die Möglichkeiten, sich direkt am IIS zu authentifizieren, sei dies über "Integrated", "Digest" oder "Basic".

Bei einer Autentifizierung über die Membership-API braucht es ein grosses Gerüst, eine Datenquelle sowie mindestens einen Membership-Provider, optional noch einen Role-Provider.
Wenn keine Datenbank verfügbar ist, bzw. keine die direkt von der Membership-API unterstützt wird, muss ein eigener Provider geschrieben werden, dies gibt einen Mehraufwand und ist nicht in allen Fällen die richtige Lösung.

Die Membership-API bietet standardmässig nur die Autentifizierungsoptionen "None, Forms, Windows oder Passport.
All diese Möglichkeiten benötigen in der Standardkonfiguration eine GUI für die Anmeldung und Verwaltung.

An dieser Stelle sollte noch die Möglichkeit erwähnt werden, eine eigene API für die Benutzerverwaltung zu schreiben, falls sich die Membership-API nicht lohnt / zu gross ist.

Ein Beispiel finden sie auf dem Blog von Jürgen Gutsch: Einfache Authentifizierung mit ASP.NET.

 

Authentifizierung ohne eigene GUI

Mit eigenen HttpHandlers lassen sich nicht nur Ressourcen-Downloads abbilden (Dateien, CSS, Javascript, ..), sondern auch komplette Webseiten.
(Merke: Die Basisklasse für eine ASP.NET Seite _Page_ ist auch ein HttpHandler, sprich: implementiert auch das Interface IHttpHandler)

Eine Möglichkeit wie sowas umgesetzt werden könnte, wurde in diesem Blog schon mal erwähnt, daneben könnte eine komplette Webapplikation mithilfe einer HttpHandlerFactory (IHttpHandlerFactory) aufgebaut werden, dazu aber in einem anderem Beitrag mehr.

Wenn jetzt ein fertiger HttpHandler besteht, der von der eigentlichen Seiteninfrastruktur losgelöst ist und dessen Aufrufe Passwort geschützt werden müssen, was dann?
Bei keiner Loslösung zur eigentlichen Applikation kann dessen Authentifizierung genutzt werden - also eine Anmeldung am System und dann Zugriff mittels Session oder Cookie.

Ist dies aber nicht der Fall, kann diese Lücke mit der HTTP Basic Autentification gefüllt werden.

 

Basic Authentification?

Das HTTP-Protokoll selber sieht eine einfache Möglichkeit zur Authentifizierung vor, die Basic Authentifizierung.
Wenn auf eine Ressource zugegriffen wird, die geschützt ist, wird beispielsweise folgender Header zum Browser geschickt:

HTTP/1.1 401 Unauthorized
Server: Microsoft-IIS/6.0
Date: Fr, 17 Oct 2008 10:22:44 GMT
WWW-Authenticate: BASIC Realm=Download Area
Connection: Keep-Alive
Content-Length: 443
Content-Type: application/pdf
Cache-control: private

Der Statuscode "401 Unauthorized" + der Header "WWW-Authenticate" + "Basic " sagt dem Browser, dass er ein Loginfenster, wie vom guten alten Verzeichnisschutz mit dem IIS bekannt ist, darstellen soll.
Dort wird ein Benutzername + Passwort eingegeben und bei Bestätigung durch [OK] zum Server gesendet.
Das Versenden geschieht im Klartext, die Benutzernamen- Passwortkombination ist lediglich Base64 codiert.

Auf dem Server wird die Kombination wiederum encodiert und geprüft.
Falls die Prüfung erfolgreich ist, wird anstelle des beschrieben Statuscodes sowie dem WWWAuthenticate-Header der eigentliche Inhalt zum Client geschickt.

Soweit die einfache Ausführung. Wie bei einer einfachen Authentifizierung mit ASP.NET kann auch hier Gebrauch vom Cookies gemacht werden, um so eine Anmeldung für einen weiteren Besuch aufrecht zu erhalten.

Eine Aufrechterhaltung mithilfe des SessionStates ist nicht nötig, da sich die Browser das letze Login merken und daher für die Dauer einer Sitzung keine weitere Eingabe mehr nötig ist, wenn nicht eine andere Kennung erforderlich ist.

Hinweis:
Beim IIS7 funktioniert das beschriebene Vorgehen in der Standardeinstellung, beim IIS6 hingegen ist in der Standardeinstellung "Integrated Windows Authentication" an, dann versucht der IIS die Anfrage selbst zu bearbeiten.
Bei den meisten Web-Providern sollte diese Einstellung standardmässig aus sein - einfach mal probieren.

iis6_setting

Beispiel?

Folgend werden sie in ein Beispiel eingeführt das die Downloads im App_Code-Ordner hält (Dadurch vom Client nicht direkt abrufbar, also geschützt) und Zugriff auf die Downloads durch einen Handler gegen Eingabe der richtigen Benutzernamen- / Passwortkombination gewährt.

Der HttpHandler im Beispiel hat zwei Methoden, neben der Implementierung vom IHttpHandler-Interface.
Wenn ein Request eintrifft, werden in der Methode "CheckLogin" jeweils die Daten in der Servervariable "HTTP_AUTHORIZATION" abgefragt.
Diese Daten enthalten im Falle einer Übergabe von Logindaten (Also wenn der Benutzer Benutzername und Passwort eingegeben hat) beispielsweise folgende Zeichenfolge:

Basic YWRtaW46cHdk

"Basic " ist die Kennung, dass es sich um die Basic Authentication handelt und die Zeichenfolge dahinter ist der Benutzername und das Password mit Base64 codiert.
Wenn diese Zeichenfolge decodiert wird, könnte sie beispielsweise so aussehen:

admin:pass

Aufgrund dieser Daten wird das Login überprüft, bei Erfolg die Datei zum Client geschickt und bei Misserfolg wiederum im Header die Daten geschickt, dass der Client sich identifizieren muss.

Dabei ist nur der Statuscode 401 + der Header nötig, die Ausgabe per Response.Write() ist optional.
"Realm" steht für den Bereich der gesichert wird, im Beispiel einfach "Download Area".



namespace BasicAuthExample.Classes
{
    public class SecureHandler : IHttpHandler
    {
        #region IHttpHandler Members

        public void ProcessRequest(HttpContext context) {
            // Login Checken
            if(CheckLogin(context)) {
                // Datei zum Client schicken
                context.Response.ContentType = "application/octet-stream";
                context.Response.AddHeader("content-disposition", "attachment; filename=test.txt");
                context.Response.WriteFile(context.Server.MapPath("~/App_Data/secure.txt"));
            } else {
                // Authentifizierung anfordern
                RequestAuth(context);
            }
        }

        public bool IsReusable {
            get {
                return false;
            }
        }

        #endregion

        private bool CheckLogin(HttpContext context) {
            // Infos vom Client (Name / Passwort)
            string data = context.Request.ServerVariables["HTTP_AUTHORIZATION"] ?? String.Empty;

            // Sind Daten einer Basic Authentifizierung vorhanden?
            bool isAuthenticationDataAvailable =
                data != String.Empty && data.IndexOf("Basic") > -1;

            // Wenn keine Daten vorhanden sind, ist das Login fehlgeschlagen
            if (!isAuthenticationDataAvailable) {
                return false;
            }

            // Ansonsten Passwort und Namen decodieren und extrahieren
            string encodedAsBase64 = data.Replace("Basic ", String.Empty);
            byte[] plainText = Convert.FromBase64String(encodedAsBase64);
            UTF8Encoding encoding = new UTF8Encoding();
            string encodedAsUtf8 = encoding.GetString(plainText);

            // Checken ob ein Doppelpunkt vorhanden ist
            int indexOfColon = encodedAsUtf8.IndexOf(':');
            if (indexOfColon == -1) {
                return false;
            }

            // Ansonsten Passwort und Namen decodieren und extrahieren
            string username = encodedAsUtf8.Substring(0, indexOfColon);
            string password = encodedAsUtf8.Substring(indexOfColon + 1);

            // Checken ob das Login korrekt ist
            if (username.Equals("admin") && password.Equals("pass")) {
                return true;
            }

            // Login fehlgeschlagen
            return false;
        }


        private void RequestAuth(HttpContext context) {
            context.Response.Write("401 Unauthorized");
            context.Response.Status = "401 Unauthorized";
            context.Response.AddHeader("WWW-Authenticate", "BASIC Realm=Download Service");
            context.Response.End();
        } 
    }
}

Wann ist diese Art der Authentifizierung jetzt überhaupt sinnvoll?

Diese Frage ist durch den ersten Teil des Artikels schon fast beantwortet.
Bei einem HttpHandler - vorallem wenn er selber von der Hauptapplikation isoliert ist (Externe Library beispielsweise) - ist es nicht einfach möglich oder sinnvoll für die Anmeldung eine riesen Infrastruktur inklusive GUI hochzufahren.

Genau bei einem solchen Fall, ist die Basic Authentifizierung ein guter Kandidat.
Wenn es um wirklich sichere Daten geht, sieht diese Art der Authentifizierung jedoch alt aus, sofern keine SSL-Verschlüsselung im Spiel ist, da alles im Klartext übermittelt wird.

Abschliessende Worte

Ich hoffe dieser Ausflug in die Authentifizierung mit ASP.NET bringt auch bei diesem Thema ein bisschen Licht ins Dunkle
und wirft hoffentlich auch neue Fragen, Ideen und Diskussionen auf.

Wie immer bin ich für alle Ideen, Vorschläge und jegliche konstruktive Kritik gerne zu haben, benutzt dazu einfach die Kommentarmöglichkeit dieses Blogs.

 

Download des Beispiels:

Quellen:

Bearbeitung / Korrekturen:

08.01.09 - CheckLogin-Methode verbessert und Download upgedated

Veröffentlicht Freitag, 17. Oktober 2008 14:55 von Peter Bucher

Kommentare

# re: Basic Authentication mit ASP.NET

Hallo. Vielen Dank für Ihren Artikel. Ich habe das so übernommen, bis auf einen Punkt funktioniert alles:

Ich habe den Handler auf zwei Dateien liegen - er springt an, alles passt. Aber ich möchte nicht wie in ihrem Beispiel nach erfolgreicher Auth. direkt in der ProcessRequest den Response schreiben, sondern die ursprüngliche Seite abarbeiten lassen.

Aber nachdem die ProcessRequest durchgelaufen ist tut sich nichts mehr.

Haben Sie eine Idee was ich falsch mache bzw. was noch fehlt?

Dienstag, 25. August 2009 19:49 by TimoA

# re: Basic Authentication mit ASP.NET

Hallo TimoA

Bitte Dutzen, das ist so üblich :-)

Danke für das Lob!

Welche ursprüngliche Seite?

Es gibt immer nur eine Seite / Ausgabe pro Request, ausser du entscheidest dich wärenddessen um (Server.Transfer() bspw.).

Den Code kannst du schlussendlich überall einbauen wo du willst, bspw. in ein HttpModule oder in eine Seite.

Bitte mehr Details, da ich dir nicht ganz folgen kann, danke.

Freitag, 28. August 2009 12:24 by Peter Bucher

# re: Basic Authentication mit ASP.NET

Hallo Peter

dann versuche ich, das ganze ein wenig ausführlicher zu beschreiben :-)

Es geht in diesem Fall um 2 Seiten, die per POST Daten empfangen:

|-in.aspx

|-dn.aspx

Per web-config Eintrag in diesem Unterordner lege ich den Handler auf die beiden Files:

<httpHandlers>

  <add verb="POST" path="*" type="BasicAuth.SecureHandler, App_Code" />

</httpHandlers>

Das funktioniert und sorgt dafür, dass bei GET/POST Anfragen die ProcessRequest des Handlers anspringt.

Dort ist alles fast gleichlaufend wie das obige Code-Beispiel, Authentifizierung wird erwartet, kommt auch und wird ausgewertet.

Allerdings wird im Anschluss an dieses Handling die eigentlich aufgerufene Seite (in.aspx, dn.aspx) nicht weiter verarbeitet.

Ich habe das gefühl evtl. grundsätzlich was falsch zu machen ;-) oder eben das Beispiel passt nicht auf meine Anforderungen.

Grüße,

Timo

Freitag, 28. August 2009 12:42 by TimoA

# re: Basic Authentication mit ASP.NET

Salute Timo

Mein Beispiel zeigt die Authentifizierung in einem Handler selber.

Das heisst der Rest läuft dann unterhalb im Handler bei ProcessRequest, wie die Textausgabe im Beispiel.

Du mappst den Handler (der nur die Auth macht) auf alles was in diesen Verzeichnissen liegt.

Das heisst, ganz egal was du requestest, nimmt nur der Handler die Anfrage entgegen.

Deine ursprünglichen Dateien werden nicht mal angefasst, da vorher der Handler anspringt.

In deinem Fall musst du die Authentifizierung entweder direkt (bspw. über eine Basisklasse) in deinen beiden Seiten erledigen, oder du nimmst dir ein HttpModule zur Hilfe, das diese Auth macht und bei erfolgreier Auth gehts weiter in der Pipeline und deine tatsächlich angeforderte Seite springt an.

HTH, Peter

Montag, 31. August 2009 16:00 by Peter Bucher
Anonyme Kommentare sind nicht zugelassen