Freigeben über


Authentifizierungsfilter in ASP.NET Web-API 2

von Mike Wasson

Ein Authentifizierungsfilter ist eine Komponente, die eine HTTP-Anforderung authentifiziert. Web-API 2 und MVC 5 unterstützen beide Authentifizierungsfilter, unterscheiden sich jedoch geringfügig, meist in den Benennungskonventionen für die Filterschnittstelle. In diesem Thema werden Web-API-Authentifizierungsfilter beschrieben.

Mit Authentifizierungsfiltern können Sie ein Authentifizierungsschema für einzelne Controller oder Aktionen festlegen. Auf diese Weise kann Ihre App unterschiedliche Authentifizierungsmechanismen für unterschiedliche HTTP-Ressourcen unterstützen.

In diesem Artikel zeige ich Code aus dem Beispiel für die Standardauthentifizierung auf https://github.com/aspnet/samples. Das Beispiel zeigt einen Authentifizierungsfilter, der das HTTP Basic Access Authentication-Schema (RFC 2617) implementiert. Der Filter wird in einer Klasse namens IdentityBasicAuthenticationAttributeimplementiert. Ich zeige nicht den gesamten Code aus dem Beispiel, nur die Teile, die veranschaulichen, wie ein Authentifizierungsfilter geschrieben wird.

Festlegen eines Authentifizierungsfilters

Wie andere Filter können Authentifizierungsfilter pro Controller, pro Aktion oder global auf alle Web-API-Controller angewendet werden.

Um einen Authentifizierungsfilter auf einen Controller anzuwenden, dekorieren Sie die Controllerklasse mit dem Filter-Attribut. Der folgende Code legt den [IdentityBasicAuthentication] Filter für eine Controllerklasse fest, wodurch die Standardauthentifizierung für alle Aktionen des Controllers aktiviert wird.

[IdentityBasicAuthentication] // Enable Basic authentication for this controller.
[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }
    public IHttpActionResult Post() { . . . }
}

Um den Filter auf eine Aktion anzuwenden, schmücken Sie die Aktion mit dem Filter. Der folgende Code legt den [IdentityBasicAuthentication] Filter für die Methode des Post Controllers fest.

[Authorize] // Require authenticated requests.
public class HomeController : ApiController
{
    public IHttpActionResult Get() { . . . }

    [IdentityBasicAuthentication] // Enable Basic authentication for this action.
    public IHttpActionResult Post() { . . . }
}

Um den Filter auf alle Web-API-Controller anzuwenden, fügen Sie ihn zu GlobalConfiguration.Filters hinzu.

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.Filters.Add(new IdentityBasicAuthenticationAttribute());

        // Other configuration code not shown...
    }
}

Implementieren eines Web-API-Authentifizierungsfilters

In der Web-API implementieren Authentifizierungsfilter die Schnittstelle "System.Web.Http.Filters.IAuthenticationFilter ". Sie sollten auch von System.Attribute erben, um als Attribute angewendet zu werden.

Die IAuthenticationFilter-Schnittstelle verfügt über zwei Methoden:

  • AuthenticateAsync authentifiziert die Anforderung, indem Anmeldeinformationen in der Anforderung überprüft werden, sofern vorhanden.
  • ChallengeAsync fügt der HTTP-Antwort bei Bedarf eine Authentifizierungsabfrage hinzu.

Diese Methoden entsprechen dem in RFC 2612 und RFC 2617 definierten Authentifizierungsfluss:

  1. Der Client sendet Anmeldeinformationen im Autorisierungsheader. Dies geschieht in der Regel, nachdem der Client eine Antwort von 401 (Nicht autorisiert) vom Server empfängt. Ein Client kann jedoch Authentifizierungsinformationen mit jeder Anforderung senden, nicht nur nach dem Abrufen eines HTTP-Statuscodes 401.
  2. Wenn der Server die Anmeldeinformationen nicht akzeptiert, wird eine Antwort von 401 (Nicht autorisiert) zurückgegeben. Die Antwort enthält eine Www-Authenticate-Kopfzeile, die mindestens eine Herausforderung enthält. Jede Abfrage gibt ein vom Server erkanntes Authentifizierungsschema an.

Der Server kann auch 401 aus einer anonymen Anforderung zurückgeben. Tatsächlich wird der Authentifizierungsprozess typischerweise so eingeleitet:

  1. Der Client sendet eine anonyme Anforderung.
  2. Der Server gibt 401 zurück.
  3. Der Client sendet die Anforderung mit Anmeldeinformationen erneut.

Dieser Fluss umfasst sowohl Authentifizierungs- als auch Autorisierungsschritte .

  • Die Authentifizierung beweist die Identität des Clients.
  • Die Autorisierung bestimmt, ob der Client auf eine bestimmte Ressource zugreifen kann.

In der Web-API behandeln Authentifizierungsfilter die Authentifizierung, aber keine Autorisierung. Die Autorisierung sollte durch einen Autorisierungsfilter oder innerhalb der Controlleraktion erfolgen.

Dies ist der Fluss in der Web-API 2-Pipeline:

  1. Bevor Sie eine Aktion aufrufen, erstellt die Web-API eine Liste der Authentifizierungsfilter für diese Aktion. Dazu gehören Filter mit Aktionsbereich, Controllerbereich und globaler Bereich.
  2. Web-API ruft AuthenticateAsync für jeden Filter in der Liste auf. Jeder Filter kann Anmeldeinformationen in der Anforderung überprüfen. Wenn ein Filter die Anmeldeinformationen erfolgreich überprüft, erstellt der Filter einen IPrincipal und fügt ihn an die Anforderung an. Ein Filter kann an diesem Punkt auch einen Fehler auslösen. Wenn dies der Fall ist, läuft der Rest der Pipeline nicht.
  3. Wenn kein Fehler auftritt, fließt die Anforderung über den Rest der Pipeline.
  4. Schließlich ruft die Web-API die ChallengeAsync-Methode jedes Authentifizierungsfilters auf. Filter verwenden diese Methode, um der Antwort bei Bedarf eine Herausforderung hinzuzufügen. Typischerweise (aber nicht immer) würde dies als Reaktion auf einen 401-Fehler geschehen.

Die folgenden Diagramme zeigen zwei mögliche Fälle. Im ersten Schritt authentifiziert der Authentifizierungsfilter die Anforderung erfolgreich, ein Autorisierungsfilter autorisiert die Anforderung, und die Controlleraktion gibt 200 (OK) zurück.

Diagramm der erfolgreichen Authentifizierung

Im zweiten Beispiel authentifiziert der Authentifizierungsfilter die Anforderung, aber der Autorisierungsfilter gibt 401 (Nicht autorisiert) zurück. In diesem Fall wird die Controlleraktion nicht aufgerufen. Der Authentifizierungsfilter fügt der Antwort einen Www-Authenticate Header hinzu.

Diagramm der nicht autorisierten Authentifizierung

Andere Kombinationen sind möglich, z. B. wenn die Controlleraktion anonyme Anforderungen zulässt, verfügen Sie möglicherweise über einen Authentifizierungsfilter, aber keine Autorisierung.

Implementieren der AuthenticateAsync-Methode

Die AuthenticateAsync-Methode versucht, die Anforderung zu authentifizieren. Dies ist die Methodensignatur:

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

Die AuthenticateAsync-Methode muss eine der folgenden Aktionen ausführen:

  1. Nichts (no-op).
  2. Erstellen Sie einen IPrincipal und legen Sie ihn für die Anforderung fest.
  3. Legen Sie ein Fehlerergebnis fest.

Option (1) bedeutet, dass die Anforderung keine Anmeldeinformationen enthält, die der Filter versteht. Option (2) bedeutet, dass der Filter die Anforderung erfolgreich authentifiziert hat. Option (3) bedeutet, dass die Anforderung ungültige Anmeldeinformationen (z. B. das falsche Kennwort) aufweist, wodurch eine Fehlerantwort ausgelöst wird.

Hier ist eine allgemeine Gliederung für die Implementierung von AuthenticateAsync.

  1. Suchen Sie nach Anmeldeinformationen in der Anforderung.
  2. Wenn keine Anmeldeinformationen vorhanden sind, unternehmen Sie nichts und kehren Sie (no-op) zurück.
  3. Wenn Anmeldeinformationen vorhanden sind, aber der Filter das Authentifizierungsschema nicht erkennt, führen Sie nichts aus und geben Sie (no-op) zurück. Ein anderer Filter in der Pipeline kann das Schema verstehen.
  4. Wenn anmeldeinformationen vorhanden sind, die der Filter versteht, versuchen Sie, sie zu authentifizieren.
  5. Wenn die Anmeldeinformationen ungültig sind, geben Sie 401 zurück, indem Sie context.ErrorResult festlegen.
  6. Wenn die Anmeldeinformationen gültig sind, erstellen Sie ein IPrincipal und setzen Sie context.Principal.

Der folgende Code zeigt die AuthenticateAsync-Methode aus dem Beispiel für die Standardauthentifizierung . Die Kommentare geben jeden Schritt an. Der Code zeigt mehrere Arten von Fehlern: Ein Autorisierungsheader ohne Anmeldeinformationen, falsch formatierte Anmeldeinformationen und ungültige Benutzernamen/Kennwort.

public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
    // 1. Look for credentials in the request.
    HttpRequestMessage request = context.Request;
    AuthenticationHeaderValue authorization = request.Headers.Authorization;

    // 2. If there are no credentials, do nothing.
    if (authorization == null)
    {
        return;
    }

    // 3. If there are credentials but the filter does not recognize the 
    //    authentication scheme, do nothing.
    if (authorization.Scheme != "Basic")
    {
        return;
    }

    // 4. If there are credentials that the filter understands, try to validate them.
    // 5. If the credentials are bad, set the error result.
    if (String.IsNullOrEmpty(authorization.Parameter))
    {
        context.ErrorResult = new AuthenticationFailureResult("Missing credentials", request);
        return;
    }

    Tuple<string, string> userNameAndPassword = ExtractUserNameAndPassword(authorization.Parameter);
    if (userNameAndPassword == null)
    {
        context.ErrorResult = new AuthenticationFailureResult("Invalid credentials", request);
    }

    string userName = userNameAndPassword.Item1;
    string password = userNameAndPassword.Item2;

    IPrincipal principal = await AuthenticateAsync(userName, password, cancellationToken);
    if (principal == null)
    {
        context.ErrorResult = new AuthenticationFailureResult("Invalid username or password", request);
    }

    // 6. If the credentials are valid, set principal.
    else
    {
        context.Principal = principal;
    }

}

Festlegen eines Fehlerergebnisses

Wenn die Anmeldeinformationen ungültig sind, muss der Filter context.ErrorResult auf ein IHttpActionResult gesetzt werden, das eine Fehlerantwort erstellt. Weitere Informationen zu IHttpActionResult finden Sie unter Action Results in Web API 2.

Das Beispiel für die Standardauthentifizierung enthält eine AuthenticationFailureResult Klasse, die für diesen Zweck geeignet ist.

public class AuthenticationFailureResult : IHttpActionResult
{
    public AuthenticationFailureResult(string reasonPhrase, HttpRequestMessage request)
    {
        ReasonPhrase = reasonPhrase;
        Request = request;
    }

    public string ReasonPhrase { get; private set; }

    public HttpRequestMessage Request { get; private set; }

    public Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        return Task.FromResult(Execute());
    }

    private HttpResponseMessage Execute()
    {
        HttpResponseMessage response = new HttpResponseMessage(HttpStatusCode.Unauthorized);
        response.RequestMessage = Request;
        response.ReasonPhrase = ReasonPhrase;
        return response;
    }
}

Implementieren von ChallengeAsync

Der Zweck der ChallengeAsync-Methode besteht darin, der Antwort bei Bedarf Authentifizierungsprobleme hinzuzufügen. Dies ist die Methodensignatur:

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

Die Methode wird für jeden Authentifizierungsfilter in der Anforderungspipeline aufgerufen.

Es ist wichtig zu verstehen, dass ChallengeAsync aufgerufen wird , bevor die HTTP-Antwort erstellt wird, und möglicherweise sogar bevor die Controlleraktion ausgeführt wird. Wenn ChallengeAsync aufgerufen wird, context.Result enthält ein IHttpActionResult, das später zum Erstellen der HTTP-Antwort verwendet wird. Wenn "ChallengeAsync " aufgerufen wird, wissen Sie also noch nichts über die HTTP-Antwort. Die ChallengeAsync-Methode sollte den ursprünglichen Wert von context.Result durch ein neues IHttpActionResult ersetzen. Dieses IHttpActionResult muss das Original context.Resultumfassen.

Diagramm von ChallengeAsync

Ich ruft das ursprüngliche IHttpActionResult als inneres Ergebnis und das neue IHttpActionResult das äußere Ergebnis auf. Das äußere Ergebnis muss Folgendes leisten:

  1. Rufen Sie das innere Ergebnis auf, um die HTTP-Antwort zu erstellen.
  2. Überprüfen Sie die Antwort.
  3. Fügen Sie der Antwort bei Bedarf eine Authentifizierungsabfrage hinzu.

Das folgende Beispiel stammt aus dem Beispiel für die Standardauthentifizierung. Es definiert ein IHttpActionResult für das äußere Ergebnis.

public class AddChallengeOnUnauthorizedResult : IHttpActionResult
{
    public AddChallengeOnUnauthorizedResult(AuthenticationHeaderValue challenge, IHttpActionResult innerResult)
    {
        Challenge = challenge;
        InnerResult = innerResult;
    }

    public AuthenticationHeaderValue Challenge { get; private set; }

    public IHttpActionResult InnerResult { get; private set; }

    public async Task<HttpResponseMessage> ExecuteAsync(CancellationToken cancellationToken)
    {
        HttpResponseMessage response = await InnerResult.ExecuteAsync(cancellationToken);

        if (response.StatusCode == HttpStatusCode.Unauthorized)
        {
            // Only add one challenge per authentication scheme.
            if (!response.Headers.WwwAuthenticate.Any((h) => h.Scheme == Challenge.Scheme))
            {
                response.Headers.WwwAuthenticate.Add(Challenge);
            }
        }

        return response;
    }
}

Die InnerResult Eigenschaft enthält das innere IHttpActionResult. Die Challenge Eigenschaft stellt eine Www-Authentication-Header dar. Beachten Sie, dass ExecuteAsync zuerst aufruft InnerResult.ExecuteAsync , um die HTTP-Antwort zu erstellen, und fügt dann bei Bedarf die Herausforderung hinzu.

Überprüfen Sie den Antwortcode, bevor Sie die Abfrage hinzufügen. Die meisten Authentifizierungsschemas fügen nur eine Herausforderung hinzu, wenn die Antwort 401 ist, wie hier gezeigt. Einige Authentifizierungsschemas stellen jedoch eine Herausforderung für eine Erfolgsantwort bereit. Siehe " Negotiate" (RFC 4559).

Angesichts der AddChallengeOnUnauthorizedResult Klasse ist der tatsächliche Code in ChallengeAsync einfach. Sie erstellen einfach das Ergebnis und fügen es an context.Result.

public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken)
{
    var challenge = new AuthenticationHeaderValue("Basic");
    context.Result = new AddChallengeOnUnauthorizedResult(challenge, context.Result);
    return Task.FromResult(0);
}

Hinweis: Im Beispiel für die Standardauthentifizierung wird diese Logik ein bisschen abstrahiert, indem sie in eine Erweiterungsmethode platziert wird.

Kombinieren von Authentifizierungsfiltern mit Host-Level Authentifizierung

Die Authentifizierung auf Hostebene wird vom Host (z. B. IIS) ausgeführt, bevor die Anforderung das Web-API-Framework erreicht.

Häufig möchten Sie die Authentifizierung auf Hostebene für die restliche Anwendung aktivieren, sie aber für Ihre Web-API-Controller deaktivieren. Ein typisches Szenario besteht beispielsweise darin, die Formularauthentifizierung auf Hostebene zu aktivieren, aber die tokenbasierte Authentifizierung für die Web-API zu verwenden.

Um die Authentifizierung auf Hostebene innerhalb der Web-API-Pipeline zu deaktivieren, rufen Sie config.SuppressHostPrincipal() in Ihrer Konfiguration auf. Dies bewirkt, dass die Web-API den IPrincipal aus jeder Anforderung entfernt, die die Web-API-Pipeline eingibt. Tatsächlich wird die Anforderung "de-authentifiziert".

public static class WebApiConfig
{
    public static void Register(HttpConfiguration config)
    {
        config.SuppressHostPrincipal();

        // Other configuration code not shown...
    }
}

Zusätzliche Ressourcen

ASP.NET Web-API-Sicherheitsfilter (MSDN Magazine)