Uwierzytelnianie i autoryzacja w internetowym interfejsie API ASP.NET

Autor: Rick Anderson

Utworzono internetowy interfejs API, ale teraz chcesz kontrolować dostęp do niego. W tej serii artykułów przyjrzymy się niektórym opcjom zabezpieczania internetowego interfejsu API przed nieautoryzowanymi użytkownikami. Ta seria obejmuje zarówno uwierzytelnianie, jak i autoryzację.

  • Uwierzytelnianie polega na poznaniu tożsamości użytkownika. Na przykład Alice loguje się przy użyciu nazwy użytkownika i hasła, a serwer używa hasła do uwierzytelniania Alicji.
  • Autoryzacja decyduje, czy użytkownik może wykonać akcję. Na przykład Alicja ma uprawnienia do pobierania zasobu, ale nie tworzy zasobu.

Pierwszy artykuł z serii zawiera ogólne omówienie uwierzytelniania i autoryzacji w ASP.NET internetowym interfejsie API. W innych tematach opisano typowe scenariusze uwierzytelniania dla internetowego interfejsu API.

Uwaga / Notatka

Dzięki ludziom, którzy przejrzeli tę serię i przekazali cenne opinie: Rick Anderson, Levi Broderick, Barry Dorrans, Tom Dykstra, Hongmei Ge, David Matson, Daniel Roth, Tim Teebken.

Uwierzytelnianie

Internetowy interfejs API zakłada, że uwierzytelnianie odbywa się na hoście. W przypadku hostingu internetowego hostem jest usługi IIS, które używają modułów HTTP do uwierzytelniania. Projekt można skonfigurować tak, aby używał dowolnych modułów uwierzytelniania wbudowanych w usługi IIS lub ASP.NET albo napisać własny moduł HTTP w celu przeprowadzenia uwierzytelniania niestandardowego.

Gdy host uwierzytelnia użytkownika, tworzy obiekt principal, który jest obiektem IPrincipal reprezentującym kontekst zabezpieczeń, w ramach którego kod jest uruchomiony. Host dołącza podmiot do bieżącego wątku, ustawiając wartość Thread.CurrentPrincipal. Podmiot zawiera skojarzony obiekt Tożsamość, który zawiera informacje o użytkowniku. Jeśli użytkownik jest uwierzytelniony, właściwość Identity.IsAuthenticated zwraca wartość true. W przypadku żądań anonimowych funkcja IsAuthenticated zwraca wartość false. Aby uzyskać więcej informacji na temat podmiotów, zobacz Role-Based Security.

Programy obsługi komunikatów HTTP na potrzeby uwierzytelniania

Zamiast używać hosta do uwierzytelniania, można umieścić logikę uwierzytelniania w procedurze obsługi komunikatów HTTP. W takim przypadku program obsługi komunikatów sprawdza żądanie HTTP i ustawia zasadę.

Kiedy należy używać programów obsługi komunikatów do uwierzytelniania? Oto pewne kompromisy:

  • Moduł HTTP widzi wszystkie żądania przechodzące przez potok ASP.NET. Obsługiwacz wiadomości widzi tylko żądania kierowane do Web API.
  • Można ustawić obsługiwacze komunikatów dla poszczególnych tras, co umożliwia zastosowanie schematu uwierzytelniania do określonej trasy.
  • Moduły HTTP są specyficzne dla usług IIS. Programy obsługi komunikatów są niezależne od hosta, dzięki czemu mogą być używane zarówno z hostingiem internetowym, jak i własnym hostingiem.
  • W usługach IIS moduły HTTP uczestniczą w rejestrowaniu, audytowaniu i tak dalej.
  • Moduły HTTP działają wcześniej w potoku przetwarzania. W przypadku obsługi uwierzytelniania w programie obsługi komunikatów podmiot zabezpieczeń nie zostanie ustawiony do momentu uruchomienia programu obsługi. Ponadto podmiot zabezpieczeń powraca do poprzedniego podmiotu zabezpieczeń, gdy odpowiedź opuszcza procedurę obsługi komunikatów.

Ogólnie rzecz biorąc, jeśli nie musisz obsługiwać własnego hostingu, moduł HTTP jest lepszym rozwiązaniem. Jeśli musisz obsługiwać samodzielne hostowanie, rozważ obsługiwacza komunikatów.

Ustawianie podmiotu głównego

Jeśli aplikacja wykonuje dowolną niestandardową logikę uwierzytelniania, musisz ustawić podmiot w dwóch miejscach:

  • Thread.CurrentPrincipal. Ta właściwość jest standardowym sposobem ustawiania głównej tożsamości wątku w .NET.
  • HttpContext.Current.User. Ta właściwość jest specyficzna dla ASP.NET.

Poniższy kod pokazuje, jak ustawić główny komponent:

private void SetPrincipal(IPrincipal principal)
{
    Thread.CurrentPrincipal = principal;
    if (HttpContext.Current != null)
    {
        HttpContext.Current.User = principal;
    }
}

W przypadku hostingu internetowego należy ustawić podstawowy kontekst zabezpieczeń w obu witrynach; w przeciwnym razie kontekst zabezpieczeń może stać się niespójny. Jednak w przypadku samodzielnego hostingu HttpContext.Current jest null. Aby upewnić się, że kod jest niezależny od hosta, sprawdź wartość null przed przypisaniem do obiektu HttpContext.Current, jak pokazano poniżej.

Autoryzacja

Autoryzacja odbywa się później w przepływie, bliżej kontrolera. Dzięki temu można dokonać bardziej szczegółowych wyborów podczas udzielania dostępu do zasobów.

  • Filtry autoryzacji są uruchamiane przed akcją kontrolera. Jeśli żądanie nie jest autoryzowane, filtr zwraca odpowiedź o błędzie, a akcja nie jest wywoływana.
  • W ramach akcji kontrolera można pobrać aktualnego użytkownika z właściwości ApiController.User. Można na przykład filtrować listę zasobów na podstawie nazwy użytkownika, zwracając tylko te zasoby, które należą do tego użytkownika.

Diagram potoku uwierzytelniania i autoryzacji.

Używanie atrybutu [Authorize]

Internetowy interfejs API udostępnia wbudowany filtr autoryzacji AuthorizeAttribute. Ten filtr sprawdza, czy użytkownik jest uwierzytelniony. Jeśli nie, zwraca kod stanu HTTP 401 (Brak autoryzacji), bez wywoływania akcji.

Filtr można zastosować globalnie, na poziomie kontrolera lub na poziomie poszczególnych akcji.

Globalnie: aby ograniczyć dostęp dla każdego kontrolera internetowego interfejsu API, dodaj filtr AuthorizeAttribute do listy filtrów globalnych:

public static void Register(HttpConfiguration config)
{
    config.Filters.Add(new AuthorizeAttribute());
}

Kontroler: Aby ograniczyć dostęp do określonego kontrolera, dodaj filtr jako atrybut do kontrolera:

// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
    public HttpResponseMessage Get(int id) { ... }
    public HttpResponseMessage Post() { ... }
}

Akcja: Aby ograniczyć dostęp do określonych akcji, dodaj atrybut do metody akcji:

public class ValuesController : ApiController
{
    public HttpResponseMessage Get() { ... }

    // Require authorization for a specific action.
    [Authorize]
    public HttpResponseMessage Post() { ... }
}

Alternatywnie można ograniczyć kontroler, a następnie zezwolić na anonimowy dostęp do określonych akcji przy użyciu atrybutu [AllowAnonymous] . W poniższym przykładzie Post metoda jest ograniczona, ale Get metoda zezwala na dostęp anonimowy.

[Authorize]
public class ValuesController : ApiController
{
    [AllowAnonymous]
    public HttpResponseMessage Get() { ... }

    public HttpResponseMessage Post() { ... }
}

W poprzednich przykładach filtr umożliwia każdemu uwierzytelnionemu użytkownikowi dostęp do ograniczonych metod; tylko anonimowi użytkownicy są wykluczani. Możesz również ograniczyć dostęp do określonych użytkowników lub użytkowników w określonych rolach:

// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
   
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}

Uwaga / Notatka

Filtr AuthorizeAttribute dla kontrolerów internetowego interfejsu API znajduje się w przestrzeni nazw System.Web.Http . W przestrzeni nazw System.Web.Mvc istnieje podobny filtr kontrolerów MVC, który nie jest zgodny z kontrolerami interfejsu API sieci Web.

Niestandardowe filtry autoryzacji

Aby napisać niestandardowy filtr autoryzacji, należy użyć jednego z następujących typów:

  • AuthorizeAttribute. Rozszerz tę klasę, aby wykonywać logikę autoryzacji na podstawie bieżącego użytkownika i ról użytkownika.
  • AuthorizationFilterAttribute. Rozszerz tę klasę, aby wykonać synchroniczną logikę autoryzacji, która nie musi być oparta na bieżącym użytkowniku lub roli.
  • IAuthorizationFilter. Zaimplementuj ten interfejs, aby wykonywać logikę autoryzacji asynchronicznej; na przykład jeśli logika autoryzacji wykonuje asynchroniczne wywołania we/wy lub sieciowe. (Jeśli logika autoryzacji jest powiązana z procesorem CPU, łatwiej jest pochodzić z atrybutu AuthorizationFilterAttribute, ponieważ nie trzeba pisać metody asynchronicznej).

Na poniższym diagramie przedstawiono hierarchię klas dla klasy AuthorizeAttribute .

Diagram hierarchii klas dla klasy Authorize Attribute.

Diagram hierarchii klas dla klasy Authorize Attribute. Atrybut Autoryzujący znajduje się na dole, ze strzałką wskazującą w górę na Atrybut Filtru Autoryzacji, i strzałką wskazującą w górę na Filtr Autoryzacji na górze.

Autoryzacja wewnątrz akcji kontrolera

W niektórych przypadkach możesz zezwolić na kontynuowanie żądania, ale zmienić zachowanie na podstawie głównego podmiotu. Na przykład zwracane informacje mogą ulec zmianie w zależności od roli użytkownika. W ramach metody kontrolera można uzyskać bieżącego głównego użytkownika z właściwości ApiController.User.

public HttpResponseMessage Get()
{
    if (User.IsInRole("Administrators"))
    {
        // ...
    }
}