Hitelesítési szűrők a ASP.NET Web API 2-ben

készítette: Mike Wasson

A hitelesítési szűrő egy HTTP-kérést hitelesítő összetevő. A Web API 2 és az MVC 5 egyaránt támogatja a hitelesítési szűrőket, de kissé eltérnek, főként a szűrőfelület elnevezési konvencióiban. Ez a témakör a webes API hitelesítési szűrőit ismerteti.

A hitelesítési szűrők lehetővé teszik az egyes vezérlők vagy műveletek hitelesítési sémájának beállítását. Így az alkalmazás különböző HITELESÍTÉSI mechanizmusokat támogat a különböző HTTP-erőforrásokhoz.

Ebben a cikkben az alapszintű hitelesítési mintából származó kódot mutatom be a következőn https://github.com/aspnet/samples: . A minta egy hitelesítési szűrőt mutat be, amely implementálja a HTTP Basic Access Authentication sémát (RFC 2617). A szűrő egy IdentityBasicAuthenticationAttribute nevű osztályban van megvalósítva. Nem jelenítem meg a mintából származó összes kódot, csak azokat a részeket, amelyek bemutatják, hogyan kell hitelesítési szűrőt írni.

Hitelesítési szűrő beállítása

A többi szűrőhöz hasonlóan a hitelesítési szűrők is alkalmazhatók vezérlőnként, műveletenként vagy globálisan az összes webes API-vezérlőre.

Ha hitelesítési szűrőt szeretne alkalmazni egy vezérlőre, díszítse a vezérlőosztályt a szűrőattribútummal. Az alábbi kód beállítja a [IdentityBasicAuthentication] szűrőt egy vezérlőosztályra, amely lehetővé teszi az alapszintű hitelesítést a vezérlő összes műveletéhez.

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

Ha a szűrőt egy műveletre szeretné alkalmazni, díszítse a műveletet a szűrővel. Az alábbi kód beállítja a szűrőt [IdentityBasicAuthentication] a vezérlő metódusára Post .

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

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

Ha a szűrőt minden webes API-vezérlőre alkalmazni szeretné, adja hozzá a GlobalConfiguration.Filters szolgáltatáshoz.

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

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

Webes API-hitelesítési szűrő implementálása

A Webes API-ban a hitelesítési szűrők implementálják a System.Web.Http.Filters.IAuthenticationFilter felületet. A System.Attribútumtól is örökölniük kell őket, hogy attribútumként legyenek alkalmazva.

Az IAuthenticationFilter interfész két módszerrel rendelkezik:

  • Az AuthenticateAsync hitelesíti a kérést a kérelem hitelesítő adatainak hitelesítésével, ha vannak ilyenek.
  • A ChallengeAsync szükség esetén hitelesítési kihívást ad a HTTP-válaszhoz.

Ezek a metódusok megfelelnek az RFC 2612-ben és az RFC2617-ben definiált hitelesítési folyamatnak:

  1. Az ügyfél hitelesítő adatokat küld az Engedélyezés fejlécben. Ez általában akkor fordul elő, ha az ügyfél 401 (jogosulatlan) választ kap a kiszolgálótól. Az ügyfél azonban bármilyen kéréssel küldhet hitelesítő adatokat, nem csak a 401-et lekérése után.
  2. Ha a kiszolgáló nem fogadja el a hitelesítő adatokat, a rendszer 401(Jogosulatlan) választ ad vissza. A válasz tartalmaz egy Www-Authenticate fejlécet, amely egy vagy több kihívást tartalmaz. Minden feladat egy, a kiszolgáló által felismert hitelesítési sémát határoz meg.

A kiszolgáló a 401-et névtelen kérésből is visszaadhatja. Valójában a hitelesítési folyamat általában így indul el:

  1. Az ügyfél névtelen kérést küld.
  2. A kiszolgáló a 401-et adja vissza.
  3. Az ügyfelek hitelesítő adatokkal újraküldik a kérést.

Ez a folyamat mind a hitelesítési , mind az engedélyezési lépéseket tartalmazza.

  • A hitelesítés igazolja az ügyfél identitását.
  • Az engedélyezés határozza meg, hogy az ügyfél hozzáfér-e egy adott erőforráshoz.

A Webes API-ban a hitelesítési szűrők kezelik a hitelesítést, de az engedélyezést nem. Az engedélyezést egy engedélyezési szűrővel vagy a vezérlőműveleten belül kell elvégezni.

A Web API 2 folyamatának folyamata a következő:

  1. A művelet meghívása előtt a Web API létrehozza a művelet hitelesítési szűrőinek listáját. Ide tartoznak a művelethatókörrel, a vezérlő hatókörével és a globális hatókörrel rendelkező szűrők.
  2. A Webes API meghívja a AuthenticateAsync-et a lista minden szűrőjén. Minden szűrő érvényesítheti a hitelesítő adatokat a kérelemben. Ha egy szűrő sikeresen ellenőrzi a hitelesítő adatokat, a szűrő létrehoz egy IPrincipal-t , és csatolja azt a kéréshez. A szűrő ezen a ponton hibát is kiválthat. Ha igen, a csővezeték többi része nem fut.
  3. Feltételezve, hogy nincs hiba, a kérés a folyamat többi részén halad át.
  4. Végül a Web API meghívja az összes hitelesítési szűrő ChallengeAsync metódusát. A szűrők ezt a módszert használják arra, hogy szükség esetén kihívást adjanak a válaszhoz. Általában (de nem mindig) ez 401-hiba esetén fordul elő.

Az alábbi ábrák két lehetséges esetet mutatnak be. Az elsőben a hitelesítési szűrő sikeresen hitelesíti a kérést, egy engedélyezési szűrő engedélyezi a kérést, a vezérlőművelet pedig 200 (OK) értéket ad vissza.

A sikeres hitelesítés diagramja

A második példában a hitelesítési szűrő hitelesíti a kérést, de az engedélyezési szűrő a 401-et (Jogosulatlan) adja vissza. Ebben az esetben a vezérlőművelet nem lesz meghívva. A hitelesítési szűrő hozzáad egy Www-Authenticate fejlécet a válaszhoz.

A jogosulatlan hitelesítés diagramja

Más kombinációk is lehetségesek – például ha a vezérlőművelet névtelen kéréseket tesz lehetővé, lehet, hogy rendelkezik hitelesítési szűrővel, de nincs engedélye.

Az AuthenticateAsync metódus implementálása

A AuthenticateAsync metódus megpróbálja hitelesíteni a kérést. A metódus aláírása a következő:

Task AuthenticateAsync(
    HttpAuthenticationContext context,
    CancellationToken cancellationToken
)

Az AuthenticateAsync metódusnak az alábbiak egyikét kell elvégeznie:

  1. Semmi (Nincs művelet).
  2. Hozzon létre egy IPrincipal-t , és állítsa be a kérelemre.
  3. Állítson be hibás eredményt.

Az (1) lehetőség azt jelenti, hogy a kérés nem rendelkezik a szűrő által megértett hitelesítő adatokkal. A (2) lehetőség azt jelenti, hogy a szűrő sikeresen hitelesítette a kérést. A (3) lehetőség azt jelenti, hogy a kérelem hitelesítő adatai érvénytelenek (például helytelen jelszóval), ami hibaválaszt vált ki.

Az alábbiakban az AuthenticateAsync implementálásának általános vázlata található.

  1. Keresse meg a hitelesítő adatokat a kérelemben.
  2. Ha nincsenek hitelesítő adatok, ne tegyen semmit, és térjen vissza (no-op).
  3. Ha vannak hitelesítő adatok, de a szűrő nem ismeri fel a hitelesítési sémát, ne tegyen semmit, és adja vissza (no-op). A folyamat egy másik szűrője is megértheti a sémát.
  4. Ha vannak olyan hitelesítő adatok, amelyeket a szűrő megért, próbálja meg hitelesíteni őket.
  5. Ha a hitelesítő adatok helytelenek, a context.ErrorResult beállítással térítse vissza a 401-et.
  6. Ha a hitelesítő adatok érvényesek, hozzon létre egy IPrincipal-t , és állítsa be context.Principal.

Az alábbi kód az Alapszintű hitelesítési mintából származó AuthenticationAsync metódust mutatja be. A megjegyzések minden lépést jeleznek. A kód számos hibatípust jelenít meg: hitelesítő adatok nélküli engedélyezési fejlécet, hibásan formázott hitelesítő adatokat és rossz felhasználónevet/jelszót.

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

}

Hibaeredmény beállítása

Ha a hitelesítő adatok érvénytelenek, a szűrőnek egy context.ErrorResult. Az IHttpActionResult szolgáltatásról további információt a Webes API 2 műveleteredményeiben talál.

Az alapszintű hitelesítési minta egy erre a célra alkalmas osztályt AuthenticationFailureResult tartalmaz.

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

A ChallengeAsync implementálása

A ChallengeAsync metódus célja, hogy szükség esetén hitelesítési kihívásokat adjon a válaszhoz. A metódus aláírása a következő:

Task ChallengeAsync(
    HttpAuthenticationChallengeContext context,
    CancellationToken cancellationToken
)

A rendszer meghívja a metódust a kérelemfolyamat minden hitelesítési szűrőjén.

Fontos tisztában lenni azzal, hogy a ChallengeAsync meghívása a HTTP-válasz létrehozása előtt , esetleg még a vezérlőművelet futtatása előtt történik. A ChallengeAsync meghívásakor context.Result tartalmaz egy IHttpActionResult parancsot, amelyet később használunk a HTTP-válasz létrehozásához. Így a ChallengeAsync meghívásakor még nem tud semmit a HTTP-válaszról. A ChallengeAsync metódusnak egy új context.Result az eredeti értéket. Ennek az IHttpActionResult-nak az eredetit context.Resultkell becsomagolnia.

ChallengeAsync diagram

Az eredeti IHttpActionResult a belső eredményt, az új IHttpActionResult pedig a külső eredményt. A külső eredménynek a következőket kell tennie:

  1. A HTTP-válasz létrehozásához hívja meg a belső eredményt.
  2. Vizsgálja meg a választ.
  3. Szükség esetén adjon hozzá hitelesítési kihívást a válaszhoz.

Az alábbi példa az alapszintű hitelesítés mintájából származik. Meghatároz egy IHttpActionResult-t a külső eredményhez.

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

A InnerResult tulajdonság a belső IHttpActionResult tulajdonságot tartalmazza. A Challenge tulajdonság egy Www-Authentication fejlécet jelöl. Figyelje meg, hogy az ExecuteAsync először meghívja InnerResult.ExecuteAsync a HTTP-választ, majd szükség esetén hozzáadja a kihívást.

A feladat hozzáadása előtt ellenőrizze a válaszkódot. A legtöbb hitelesítési séma csak akkor ad kihívást, ha a válasz 401, ahogy az itt látható. Egyes hitelesítési sémák azonban kihívást jelentenek a sikeres válaszhoz. Lásd például az Egyeztetés (RFC 4559) című témakört.

Az osztály alapján AddChallengeOnUnauthorizedResult a ChallengeAsync tényleges kódja egyszerű. Hozza létre az eredményt, és csatolja a context.Result-hoz.

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

Megjegyzés: Az alapszintű hitelesítési minta ezt a logikát kissé elvonatkoztatja, elhelyezve azt egy bővítménymetódusban.

Hitelesítési szűrők kombinálása Host-Level hitelesítéssel

A "gazdagépszintű hitelesítés" az a hitelesítés, amelyet a gazdagép (például az IIS) hajt végre, mielőtt a kérés eléri a webes API-keretrendszert.

Előfordulhat, hogy az alkalmazás többi részében engedélyezni szeretné a gazdagépszintű hitelesítést, de letilthatja a webes API-vezérlők esetében. Például egy tipikus forgatókönyv az, hogy a Forms Authentication-t a gazda szintjén engedélyezzük, míg a Web API esetében jogkivonat-alapú hitelesítést használunk.

Ha le szeretné tiltani a gazdagépszintű hitelesítést a webes API pipeline-ban, hívja meg config.SuppressHostPrincipal() a konfigurációban. Ez azt eredményezi, hogy a Webes API eltávolítja az IPrincipal-t a webes API-folyamatba belépő kérésekből. Gyakorlatilag "nem hitelesíti" a kérést.

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

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

További források

ASP.NET Webes API biztonsági szűrői (MSDN Magazin)