Sie sind hier: Weblog

Kommunikation zwischen Anwendungscode und Katana-Middleware-Komponenten

Foto ,
15.01.2014 21:08:00

Der Anwendungscode kann unter Verwendung des AuthenticationManagers mit den Handlern von Authentication-Middleware-Komponenten kommunizieren. Ein Beispiel dafür findet sich in meinem Beitrag unter [1], wo unter Verwendung der Methode AuthenticateAsync die passive Middleware-Komponente aufgefordert wird, den Benutzer zu authentifizieren. Kenntnisse darüber helfen beim Verständnis von bestehenden Middleware-Komponenten, allen voran jene zur Authentifizierung mit externen Login-Providern, wie Google oder Facebook. Darüber hinaus sind diese Mechanismen essentiell zur Umsetzung eigener Authentication-Middleware-Komponenten. Ein Beispiel dafür findet sich unter [2].

Der Anwendungscode kann mit den folenden Handler-Methoden kommunizieren: AuthenticateCoreAsync, ApplyResponseGrantAsync und ApplyResponseChallengeAsync. Um die Art der Kommunikation zwischen Anwendungscode und den hier genannten Methoden zu verstehen, muss man sich die damit einhergehende Aufrufreihenfolge vor Augen halten. Beschränkt man diese Betrachtung auf den Anwendungscode und eine einzige Middleware, gestaltet sich die Aufrufreihenfolge wie folgt, wenn sie auf die genannten Methoden beschränkt wird:

1. AuthenticateCoreAsync (falls es sich um eine aktive Middleware handelt)
2. Anwendungscode
3. ApplyResponseGrantAsync
4. ApplyResponseChallengeAsync

Die Weitergabe von Informationen erfolgt entlang dieser Aufrufkette. AuthenticateCoreAsync retourniert ein AuthenticationTicket, welches vom Anwendungscode entgegengenommen werden kann. Um an die Informationen in diesem Ticket zu kommen, ruft der Anwendungscode, wie im nächsten Listing gezeigt, die Methode AuthenticateAsync des AuthenticationManagers auf und übergibt dabei den AuthenticationType der jeweiligen Middleware-Komponente. Wird die Middleware im aktiven Modus ausgeführt, retourniert diese Methode jene Informationen, die AuthenticateCoreAsync zuvor ermittelt und retourniert hat. Wird die Middleware hingegen im passiven Modus ausgeführt, bringt der Aufruf von AuthenticateAsync, wie im Abschnitt „Einsatz als passive Authentifizierungs-Middleware“ besprochen, die Methode AuthenticateCoreAsync im Handler zur Ausführung und retourniert anschließend ebenfalls die von ihr ermittelten Informationen in Form einer Instanz von AuthenticationResult.

Dieses AuthenticationResult bietet unter anderem über eine Eigenschaften die IIdentity-Instanz und die AuthenticationProperties-Instanz aus dem AuthenticationTicket. Möchte die Methode AuthenticateCoreAsync dem Anwendungscode zusätzliche Informationen zukommen lassen, kann sie hierzu die Eigenschaft Dictionary der über das Ticket retournierten AuthenticationProperties-Instanz verwenden. Dabei handelt es sich um ein IDictionary<string,string>.

 

var identity = new ClaimsIdentity(Options.AuthenticationType);
AuthenticationProperties prop = new AuthenticationProperties();
prop.Dictionary["WasStrongPassword"] = "true";
var ticket = new AuthenticationTicket(identity, prop);
return Task.FromResult(ticket);
Der Anwendungscode kann über die erhaltene AuthenticationProperties-Instanz darauf zugreifen:
var authenticationManager = Request.GetOwinContext().Authentication;
var authResult = await authenticationManager.AuthenticateAsync("Basic");
if (authResult != null)
{
    String wasStrongPassword = authResult.Properties.Dictionary["WasStrongPassword"]);
    […]
}

 

Um die Methode ApplyResponseGrantAsync des Handlers aufzufordern, einen Benutzer anzumelden, ruft der Anwendungscode die Methode SignIn des AuthenticationManagers auf; um ApplyResponseGrantAsync aufzufordern, einen Benutzer abzumelden, verwendet der Anwendungscode SignOff. Um mit der Methode ApplyResponseChallengeAsync im Handler zu kommunizieren, verwendet der Anwendungscode die Methode Challange des AuthenticationManagers. Das nächste Listing veranschaulicht die Verwendung dieser Methoden.

An SignOut und Challange übergibt der Aufrufer den AuthenticationType der gewünschten Middleware-Komponenten. Um mehrere Middleware-Komponenten zu adressieren, kann er hier auch mehrere AuthenticationTypes übergeben. SignIn entnimmt stattdessen den AuthenticationType der gewünschten Middleware aus der übergebenen ClaimsIdentity, welche die Identität des anzumeldenden Benutzers wiederspiegelt. Hat der Benutzer mehrere Identitäten, kann der Aufrufer auch mehrere ClaimsIdentity-Instanzen anführen.

SignIn und Challange nehmen auch eine Instanz von AuthenticationProperties entgegen. Über dessen Eigenschaft Dictionary kann der Anwendungscode diesen Methoden zusätzliche Informationen zukommen lassen.

 

var authenticationManager = Request.GetOwinContext().Authentication;

var p = new AuthenticationProperties();
p.Dictionary["info"] = "Some additional information";

authenticationManager.SignIn(p, new ClaimsIdentity("Basic"));
authenticationManager.SignOut("Basic");
authenticationManager.Challenge(p, "Basic");

 

Um in der Methode ApplyResponseGrantAsync des Handlers herauszufinden, ob der Anwendungscode die Methode SignIn aufgerufen hat, verwendet diese die Methode LookupSignIn der geerbten Eigenschaft Helper (siehe nachfolgendes). An diese ist der eigene AuthenticationType zu übergeben. Retourniert LookupSignIn den Wert null, hat der Anwendungscode SignIn nicht aufgerufen. Ansonsten finden sich im auf diesem Weg erhaltenen AuthenticationResponseGrant die an SignIn übergebenen Informationen.

Ähnlich gestaltet es sich mit SignOut und der Methode LookupSignOut des geerbten Helpers: Liefert LookupSignOut den Wert null, hat der Anwendungscode die Methode SignOut nicht aufgerufen; Ansonsten finden sich im retournierten AuthenticationResponseRevoke die an SignOut übergebenen Infos, welche sich auf die angegebenen AuthenticationTypes beschränken.

Im Gegensatz zu LookupSignIn erwartet LookupSignOut neben dem AuthenticationType auch den verwendeten AuthenticationMode.

 

protected override async Task ApplyResponseGrantAsync()
{
    AuthenticationResponseGrant grant = base.Helper.LookupSignIn(base.Options.AuthenticationType);
    AuthenticationResponseRevoke revoke = base.Helper.LookupSignOut(
                                               base.Options.AuthenticationType, 
                                               base.Options.AuthenticationMode);
    if (grant != null)
    {
        // SignIn requested
        if (grant.Properties.Dictionary.ContainsKey("info"))
        {
            var info = grant.Properties.Dictionary["info"];
            […]
        }
        var identity = grant.Identity;
        var principal = grant.Principal;
        […]
    }

    if (revoke != null)
    {
        // SignOut requested
        var authTypes = revoke.AuthenticationTypes;
        […]
    }

}

 

Dasselbe Prinzip kann innerhalb der Methode ApplyResponseChallengeAsync des Handlers angewendet werden, um herauszufinden, ob der Anwendungscode Challange aufgerufen hat. Hierzu bietet der geerbte Helper eine Methode LookupChallange, welche eine AuthenticationResponseChallenge retourniert (siehe nachfolgedes Listing). Um herauszufinden, ob der Anwendungscode die Methode Challange aufgerufen hat, reicht es hier jedoch nicht, nur die AuthenticationResponseChallenge gegen null zu prüfen. Zusätzlich sollte auch ihre Eigenschaft AuthenticationTypes auf null geprüft werden.

 

protected override async Task ApplyResponseChallengeAsync()
{
    AuthenticationResponseChallenge challange = base.Helper.LookupChallenge(
                                                        base.Options.AuthenticationType, 
                                                        base.Options.AuthenticationMode);
    if (challange != null && challange.AuthenticationTypes != null)
    {
        // Challange requested
        if (challange.Properties != null && challange.Properties.Dictionary.ContainsKey("info"))
        {
            var info = challange.Properties.Dictionary["info"];
            Response.Write(info);
        }
        var authTypes = challange.AuthenticationTypes;
                
    }

    if (this.Response.StatusCode == 401)
    {
        Response.Headers.Add("WWW-Authenticate", new [] { "Basic" });
    }
}

 

Ob die Methoden ApplyResponseGrantAsync und ApplyResponseChallengeAsync von sich aus aktiv werden, oder nur, wenn sie vom Anwendungscode dazu aufgefordert werden, ist ein Implementierungsdatei der Middleware. Denkbar ist auch eine Mischform. Im Übrigen kann nicht verhindert werden, dass Methoden, die nach dem Anwendungscode ausgeführt werden, unter Verwendung des Helpers an Informationen kommen, die für sie gar nicht bestimmt sind. So kann beispielsweise die Methode ApplyResponseChallengeAsync ohne weiteres die Methoden LookupSignIn und LookupSignOut bemühen, um an die Informationen zu kommen, die eigentlich für ApplyResponseGrantAsync sind.

[1] http://www.softwarearchitekt.at/post/2013/09/01/Passive-Authentifizierungs-Middleware-mit-OWINKatana-entwickeln.aspx
[2] http://www.softwarearchitekt.at/post/2013/08/20/Benutzerdefinierte-Authentifizierungs-Strategien-in-ASPNET-vNext-mit-KatanaOWIN.aspx