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 a MVC 5 podporují ověřovací filtry, ale mírně se liší, 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/samplesadrese . Ukázka ukazuje ověřovací filtr, který implementuje schéma základního ověřování přístupu HTTP (RFC 2617). Filtr je implementován ve třídě s názvem IdentityBasicAuthenticationAttribute. Nebudu zobrazovat celý kód z ukázky, jenom ty části, které ukazují, jak napsat ověřovací filtr.

Nastavení ověřovacího filtru

Podobně jako jiné filtry je možné filtry ověřování použít na kontroler, akci nebo globálně na všechny kontrolery webového rozhraní API.

Chcete-li použít ověřovací filtr na kontroleru, ozdobte třídu kontroleru atributem filter. Následující kód nastaví [IdentityBasicAuthentication] filtr na třídu 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() { . . . }
}

Chcete-li použít filtr na jednu akci, ozdobte akci filtrem. Následující kód nastaví [IdentityBasicAuthentication] filtr na metodu 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 použít filtr 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 z System.Attribute, aby se použily jako atributy.

Rozhraní IAuthenticationFilter má dvě metody:

  • AuthenticateAsync ověřuje požadavek ověřením přihlašovacích údajů v požadavku, pokud jsou 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 rfc 2612 a RFC 2617:

  1. Klient odešle přihlašovací údaje v hlavičce Autorizace. K tomu obvykle dochází poté, co klient obdrží ze serveru odpověď 401 (Neautorizováno). Klient ale může odesílat přihlašovací údaje s libovolným požadavkem, ne jen 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 jednu nebo více výzev. Každý úkol určuje schéma ověřování rozpoznané serverem.

Server může také vrátit 401 z anonymní žádosti. Ve skutečnosti se proces ověřování obvykle inicializoval takto:

  1. Klient odešle anonymní žádost.
  2. Server vrátí hodnotu 401.
  3. Klienti znovu odešle požadavek 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 ke konkrétnímu prostředku.

Ve webovém rozhraní API filtry ověřování zpracovávají ověřování, ale ne autorizaci. Autorizace by se měla provádět pomocí filtru autorizace nebo uvnitř akce kontroleru.

Tady je tok v kanálu webového rozhraní API 2:

  1. Před vyvoláním akce webové rozhraní API vytvoří seznam ověřovacích filtrů pro danou akci. To zahrnuje filtry s rozsahem akce, oborem kontroleru a globálním oborem.
  2. Webové rozhraní API volá AuthenticateAsync pro každý filtr v seznamu. Každý filtr může ověřit přihlašovací údaje v požadavku. Pokud některý filtr úspěšně ověří přihlašovací údaje, vytvoří filtr IPrincipal a připojí ho k požadavku. Filtr může v tomto okamžiku také aktivovat chybu. Pokud ano, zbytek kanálu se nespustí.
  3. Za předpokladu, že nedojde k žádné chybě, požadavek projde zbytkem kanálu.
  4. Nakonec webové rozhraní API volá metodu ChallengeAsync každého ověřovacího filtru. Filtry používají tuto metodu k přidání výzvy k odpovědi v případě potřeby. Obvykle (ale ne vždy), ke kterému by došlo v reakci na chybu 401.

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

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

Ve druhém příkladu ověřovací filtr požadavek ověří, 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 povoluje anonymní požadavky, můžete mít ověřovací filtr, ale bez autorizace.

Implementace metody AuthenticateAsync

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

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

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

  1. Nic (bez operace).
  2. Vytvořte IPrincipal a nastavte ho v požadavku.
  3. Nastavte výsledek chyby.

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), které aktivují chybovou odpověď.

Tady je obecný přehled implementace AuthenticateAsync.

  1. V požadavku vyhledejte přihlašovací údaje.
  2. Pokud nejsou k dispozici žádné přihlašovací údaje, nedělejte nic a vraťte se (bez operace).
  3. Pokud existují přihlašovací údaje, ale filtr nerozpozná schéma ověřování, nedělejte nic a vraťte se (bez operace). Schéma může pochopit jiný filtr v kanálu.
  4. Pokud filtr rozumí přihlašovacím údajům, zkuste je ověřit.
  5. Pokud jsou přihlašovací údaje chybné, vraťte 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 zobrazuje několik typů chyb: Autorizační hlavička bez přihlašovacích údajů, poškozené přihlašovací údaje a chybné uživatelské jméno a heslo.

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í výsledku chyby

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

Ukázka základního AuthenticationFailureResult ověřování obsahuje třídu, 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 této metody:

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

Metoda je volána u každého ověřovacího filtru v kanálu požadavku.

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

Diagram of ChallengeAsync

Původní IHttpActionResult zavolám vnitřní výsledek a nový IHttpActionResultvnější výsledek. Vnější výsledek musí být následující:

  1. Vyvolání vnitřního výsledku pro vytvoření odpovědi HTTP
  2. Podívejte se, jaká bude 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 základního ověřování. 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 zavolá InnerResult.ExecuteAsync a vytvoří odpověď HTTP a 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 tady. Některá schémata ověřování ale přidávají výzvu k úspěšné odpovědi. Viz například Negotiate (RFC 4559).

Vzhledem k AddChallengeOnUnauthorizedResult třídě je skutečný kód v ChallengeAsync jednoduchý. Stačí vytvořit výsledek a připojit 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 rozšiřující metody.

Kombinace ověřovacích filtrů s ověřováním Host-Level

Ověřování na úrovni hostitele je ověřování prováděné hostitelem (například službou IIS) před tím, než požadavek dorazí do rozhraní 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 použití ověřování na základě tokenů pro webové rozhraní API.

Pokud chcete zakázat ověřování na úrovni hostitele v kanálu webového rozhraní API, zavolejte config.SuppressHostPrincipal() ve své konfiguraci. To způsobí, že webové rozhraní API odebere IPrincipal ze všech požadavků, které vstoupí do kanálu webového rozhraní API. Ve skutečnosti "zruší" ověření požadavku.

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

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

Další materiály

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