Filtry ověřování ve webovém rozhraní API ASP.NET 2

Mike Wasson

Ověřovací filtr je komponenta, která ověřuje požadavek HTTP. Webové rozhraní API 2 i MVC 5 podporují filtry ověřování, ale liší se mírně, většinou v konvencích vytváření názvů pro rozhraní filtru. Toto téma popisuje filtry ověřování webového rozhraní API.

Filtry ověřování umožňují nastavit schéma ověřování pro jednotlivé kontrolery nebo akce. Aplikace tak může podporovat různé mechanismy ověřování pro různé prostředky HTTP.

V tomto článku zobrazím kód z ukázky základního ověřování na https://github.com/aspnet/samples. Ukázka ukazuje ověřovací filtr, který implementuje schéma ověřování základního přístupu HTTP (RFC 2617). Filtr je implementován ve třídě s názvem IdentityBasicAuthenticationAttribute. Nezobrazím veškerý kód z ukázky, jenom části, které ilustrují, jak napsat ověřovací filtr.

Nastavení ověřovacího filtru

Stejně jako u jiných filtrů je možné použít filtry ověřování pro kontroler, jednotlivou akci nebo globálně na všechny kontrolery webového rozhraní API.

Chcete-li použít ověřovací filtr na kontroler, ozdobte třídu kontroleru atributem filtru. Následující kód nastaví [IdentityBasicAuthentication] filtr třídy kontroleru, který umožňuje základní ověřování pro všechny akce kontroleru.

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

Pokud chcete filtr použít na jednu akci, ozdobte akci filtrem. Následující kód nastaví [IdentityBasicAuthentication] filtr metody kontroleru Post .

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

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

Pokud chcete filtr použít na všechny kontrolery webového rozhraní API, přidejte ho do GlobalConfiguration.Filters.

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

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

Implementace filtru ověřování webového rozhraní API

Ve webovém rozhraní API implementují filtry ověřování rozhraní System.Web.Http.Filters.IAuthenticationFilter . Měly by také dědit ze System.Attribute, aby byly použity jako atributy.

Rozhraní IAuthenticationFilter má dvě metody:

  • AuthenticateAsync ověří požadavek ověřením přihlašovacích údajů v požadavku, pokud je k dispozici.
  • ChallengeAsync v případě potřeby přidá do odpovědi HTTP ověřovací výzvu.

Tyto metody odpovídají toku ověřování definovanému v DOKUMENTU RFC 2612 a RFC 2617:

  1. Klient odešle přihlašovací údaje v hlavičce autorizace. K tomu obvykle dochází po přijetí odpovědi 401 (Neautorizováno) ze serveru. Klient ale může posílat přihlašovací údaje s libovolným požadavkem, nejen po získání 401.
  2. Pokud server nepřijme přihlašovací údaje, vrátí odpověď 401 (Neautorizováno). Odpověď obsahuje hlavičku Www-Authenticate, která obsahuje jeden nebo více problémů. Každá výzva určuje schéma ověřování rozpoznané serverem.

Server může také vrátit hodnotu 401 z anonymního požadavku. Ve skutečnosti je to obvykle způsob, jakým se proces ověřování inicializoval:

  1. Klient odešle anonymní požadavek.
  2. Server vrátí hodnotu 401.
  3. Klient znovu odesílá žádost s přihlašovacími údaji.

Tento tok zahrnuje postup ověřování i autorizace .

  • Ověřování prokáže identitu klienta.
  • Autorizace určuje, jestli má klient přístup k určitému prostředku.

Ve webovém rozhraní API filtry ověřování zpracovávají ověřování, ale ne autorizaci. Autorizaci by měl provést autorizační filtr nebo uvnitř akce kontroleru.

Tady je průběh v potrubí webového rozhraní API 2:

  1. Před vyvoláním akce vytvoří webové rozhraní API seznam ověřovacích filtrů pro danou akci. To zahrnuje filtry s rozsahem akcí, oborem kontroleru a globálním oborem.
  2. Webové rozhraní API volá funkci AuthenticateAsync pro každý filtr v seznamu. Každý filtr může ověřit přihlašovací údaje v požadavku. Pokud nějaký filtr úspěšně ověří přihlašovací údaje, vytvoří filtr IPrincipal a připojí ho k požadavku. V tuto chvíli může filtr také vyvolat chybu. Pokud ano, zbytek kanálu se nespustí.
  3. Za předpokladu, že nedojde k chybě, požadavek prochází zbytkem pipeliny.
  4. Nakonec webové rozhraní API volá každou metodu ChallengeAsync filtru ověřování. Filtry tuto metodu používají k přidání výzvy k odpovědi v případě potřeby. Typicky (ale ne vždy) by k tomu došlo jako reakce na chybu 401.

Následující diagramy znázorňují dva možné případy. V první části ověřovací filtr úspěšně ověří požadavek, autorizační filtr žádost autorizuje a akce kontroleru vrátí hodnotu 200 (OK).

Diagram úspěšného ověřování

V druhém příkladu ověřovací filtr ověřuje požadavek, ale autorizační filtr vrátí hodnotu 401 (Neautorizováno). V tomto případě není vyvolána akce kontroleru. Ověřovací filtr přidá do odpovědi hlavičku Www-Authenticate.

Diagram neoprávněného ověřování

Jsou možné i jiné kombinace – například pokud akce kontroleru umožňuje anonymní požadavky, můžete mít filtr ověřování, ale bez autorizace.

Implementace metody AuthenticateAsync

Metoda AuthenticateAsync se pokusí ověřit požadavek. Tady je podpis metody:

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

Metoda AuthenticateAsync musí provést jednu z následujících věcí:

  1. Nic (no-op).
  2. Vytvořte objekt IPrincipal a nastavte ho na žádost.
  3. Nastavte chybový výsledek.

Možnost (1) znamená, že požadavek neměl žádné přihlašovací údaje, kterým filtr rozumí. Možnost (2) znamená, že filtr požadavek úspěšně ověřil. Možnost (3) znamená, že požadavek měl neplatné přihlašovací údaje (například nesprávné heslo), což aktivuje chybovou odpověď.

Tady je obecný přehled implementace protokolu AuthenticateAsync.

  1. V požadavku vyhledejte přihlašovací údaje.
  2. Pokud neexistují žádné přihlašovací údaje, nic nedělejte a vraťte (no-op).
  3. Pokud existují přihlašovací údaje, ale filtr nerozpozná schéma ověřování, nic nedělejte a vraťte se (no-op). Další filtr v kanálu může pochopit schéma.
  4. Pokud filtr rozumí přihlašovacím údajům, zkuste je ověřit.
  5. Pokud jsou přihlašovací údaje chybné, vraťte hodnotu 401 nastavením context.ErrorResult.
  6. Pokud jsou přihlašovací údaje platné, vytvořte IPrincipal a nastavte context.Principal.

Následující kód ukazuje metodu AuthenticateAsync z ukázky základního ověřování . Komentáře označují jednotlivé kroky. Kód ukazuje několik typů chyb: Autorizační hlavička bez přihlašovacích údajů, poškozených přihlašovacích údajů a chybného uživatelského jména a hesla.

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;
    }

}

Nastavení chybného výsledku

Pokud jsou přihlašovací údaje neplatné, filtr musí nastavit context.ErrorResult na IHttpActionResult, který vytvoří chybovou odpověď. Další informace o IHttpActionResult naleznete v tématu Výsledky akcí ve webovém rozhraní API 2.

Ukázka Basic Authentication obsahuje třídu AuthenticationFailureResult, která je pro tento účel vhodná.

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;
    }
}

Implementace ChallengeAsync

Účelem metody ChallengeAsync je v případě potřeby přidat do odpovědi výzvy ověřování. Tady je podpis metody:

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

Metoda se volá pro každý filtr ověřování v kanálu požadavku.

Je důležité si uvědomit, že Před vytvořením odpovědi HTTP se voláChallengeAsync a možná i před spuštěním akce kontroleru. Když je volána funkce ChallengeAsync, context.Result obsahuje IHttpActionResult, který se později použije k vytvoření odpovědi HTTP. Takže když se volá ChallengeAsync , ještě nic o odpovědi HTTP neznáte. Metoda ChallengeAsync by měla nahradit původní hodnotu context.Result novou IHttpActionResult. Tento IHttpActionResult musí zabalit původní context.Result.

Diagram of ChallengeAsync

Označím původní IHttpActionResult jako vnitřní výsledek, a nový IHttpActionResult jako vnější výsledek. Vnější výsledek musí splňovat následující:

  1. Vyvolejte vnitřní výsledek a vytvořte odpověď HTTP.
  2. Prozkoumejte odpověď.
  3. V případě potřeby přidejte do odpovědi ověřovací výzvu.

Následující příklad je převzat z ukázky Basic autentizace. Definuje IHttpActionResult pro vnější výsledek.

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;
    }
}

Vlastnost InnerResult obsahuje vnitřní IHttpActionResult. Vlastnost Challenge představuje hlavičku Www-Authentication. Všimněte si, že ExecuteAsync nejprve volá, InnerResult.ExecuteAsync aby vytvořila odpověď HTTP, a pak v případě potřeby přidá výzvu.

Před přidáním výzvy zkontrolujte kód odpovědi. Většina schémat ověřování přidává výzvu pouze v případě, že odpověď je 401, jak je znázorněno zde. Některá schémata ověřování ale přidají výzvu k úspěšné odpovědi. Například viz Negotiate (RFC 4559).

Vzhledem ke AddChallengeOnUnauthorizedResult třídě je skutečný kód v ChallengeAsync jednoduchý. Jednoduše vytvoříte výsledek a připojíte ho k 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);
}

Poznámka: Ukázka základního ověřování tuto logiku trochu abstrahuje tím, že ji umístí do metody rozšíření.

Kombinování ověřovacích filtrů s ověřováním na úrovni hostitele

"Ověřování na úrovni hostitele" je ověřování prováděné hostitelem (například IIS), než požadavek dosáhne architektury webového rozhraní API.

Často můžete chtít povolit ověřování na úrovni hostitele pro zbytek vaší aplikace, ale zakázat ho pro kontrolery webového rozhraní API. Typickým scénářem je například povolení ověřování pomocí formulářů na úrovni hostitele, ale pro webové rozhraní API použijte ověřování založené na tokenech.

Pokud chcete deaktivovat ověřování na úrovni hostitele v potrubí webového rozhraní API, zavolejte config.SuppressHostPrincipal() v konfiguraci. Toto způsobí, že Web API odstraní IPrincipal z každého požadavku, který vstoupí do kanálu Web API. Efektivně to "zruší autentizaci" požadavku.

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

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

Další zdroje

ASP.NET Filtry zabezpečení webového rozhraní API (MSDN Magazine)