Samouczek: zabezpieczanie internetowego interfejsu API platformy ASP.NET Core zarejestrowanego w dzierżawie zewnętrznej

W tej serii samouczków pokazano, jak zabezpieczyć zarejestrowany internetowy interfejs API w dzierżawie zewnętrznej. W tym samouczku utworzysz internetowy interfejs API platformy ASP.NET Core, który publikuje uprawnienia delegowane (zakresy) i uprawnienia aplikacji (role aplikacji).

W tym samouczku;

  • Konfigurowanie internetowego interfejsu API do używania szczegółów rejestracji aplikacji
  • Konfigurowanie internetowego interfejsu API do używania uprawnień delegowanych i aplikacji zarejestrowanych w rejestracji aplikacji
  • Ochrona punktów końcowych internetowego interfejsu API

Wymagania wstępne

Tworzenie internetowego interfejsu API platformy ASP.NET Core

  1. Otwórz terminal, a następnie przejdź do folderu, w którym ma działać projekt.

  2. Uruchom następujące polecenia:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. Gdy zostanie wyświetlone okno dialogowe z pytaniem, czy chcesz dodać wymagane zasoby do projektu, wybierz pozycję Tak.

Instalowanie pakietów

Zainstaluj następujące pakiety:

  • Microsoft.EntityFrameworkCore.InMemory umożliwia korzystanie z programu Entity Framework Core z bazą danych w pamięci. Nie jest przeznaczony do użytku produkcyjnego.
  • Microsoft.Identity.WebUpraszcza dodawanie obsługi uwierzytelniania i autoryzacji do aplikacji internetowych i internetowych interfejsów API integrujących się z Platforma tożsamości Microsoft.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Konfigurowanie szczegółów rejestracji aplikacji

Otwórz plik appsettings.json w folderze aplikacji i dodaj szczegóły rejestracji aplikacji zarejestrowane po zarejestrowaniu internetowego interfejsu API.

{
    "AzureAd": {
        "Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here",
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

Zastąp następujące symbole zastępcze, jak pokazano:

  • Zastąp Enter_the_Application_Id_Here element identyfikatorem aplikacji (klienta).
  • Zastąp Enter_the_Tenant_Id_Here ciąg identyfikatorem katalogu (dzierżawy).
  • Zastąp Enter_the_Tenant_Subdomain_Here ciąg domeną podrzędną Katalog (dzierżawa).

Dodawanie roli i zakresu aplikacji

Wszystkie interfejsy API muszą opublikować co najmniej jeden zakres, nazywany również uprawnieniem delegowanym, aby aplikacje klienckie pomyślnie uzyskały token dostępu dla użytkownika. Interfejsy API powinny również publikować co najmniej jedną rolę aplikacji dla aplikacji, nazywanych również uprawnieniami aplikacji, aby aplikacje klienckie mogły uzyskać token dostępu jako siebie, czyli wtedy, gdy użytkownik nie loguje się.

Określamy te uprawnienia w pliku appsettings.json . W tym samouczku zarejestrowano cztery uprawnienia. ToDoList.ReadWrite i ToDoList.Read jako uprawnienia delegowane oraz ToDoList.ReadWrite.All i ToDoList.Read.All jako uprawnienia aplikacji.

{
  "AzureAd": {
    "Instance": "https://Enter_the_Tenant_Subdomain_Here.ciamlogin.com/",
    "TenantId": "Enter_the_Tenant_Id_Here",
    "ClientId": "Enter_the_Application_Id_Here",
    "Scopes": {
      "Read": ["ToDoList.Read", "ToDoList.ReadWrite"],
      "Write": ["ToDoList.ReadWrite"]
    },
    "AppPermissions": {
      "Read": ["ToDoList.Read.All", "ToDoList.ReadWrite.All"],
      "Write": ["ToDoList.ReadWrite.All"]
    }
  },
  "Logging": {...},
  "AllowedHosts": "*"
}

Dodawanie schematu uwierzytelniania

Schemat uwierzytelniania jest nazwany, gdy usługa uwierzytelniania jest skonfigurowana podczas uwierzytelniania. W tym artykule używamy schematu uwierzytelniania elementu nośnego JWT. Dodaj następujący kod w pliku Programs.cs, aby dodać schemat uwierzytelniania.

// Add the following to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

// Add authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);

Tworzenie modeli

Utwórz folder o nazwie Models w folderze głównym projektu. Przejdź do folderu i utwórz plik o nazwie ToDo.cs następnie dodaj następujący kod. Ten kod tworzy model o nazwie ToDo.

using System;

namespace ToDoListAPI.Models;

public class ToDo
{
    public int Id { get; set; }
    public Guid Owner { get; set; }
    public string Description { get; set; } = string.Empty;
}

Dodawanie kontekstu bazy danych

Kontekst bazy danych jest klasą główną, która koordynuje funkcje programu Entity Framework dla modelu danych. Ta klasa jest tworzona przez wyprowadzanie z klasy Microsoft.EntityFrameworkCore.DbContext . W tym samouczku używamy bazy danych w pamięci do celów testowych.

  1. Utwórz folder o nazwie DbContext w folderze głównym projektu.

  2. Przejdź do tego folderu i utwórz plik o nazwie ToDoContext.cs następnie dodaj następującą zawartość do tego pliku:

    using Microsoft.EntityFrameworkCore;
    using ToDoListAPI.Models;
    
    namespace ToDoListAPI.Context;
    
    public class ToDoContext : DbContext
    {
        public ToDoContext(DbContextOptions<ToDoContext> options) : base(options)
        {
        }
    
        public DbSet<ToDo> ToDos { get; set; }
    }
    
  3. Otwórz plik Program.cs w folderze głównym aplikacji, a następnie dodaj następujący kod w pliku. Ten kod rejestruje podklasę DbContext o nazwie ToDoContext jako usługę o określonym zakresie w dostawcy usługi aplikacji ASP.NET Core (znany również jako kontener wstrzykiwania zależności). Kontekst jest skonfigurowany do używania bazy danych w pamięci.

    // Add the following to your imports
    using ToDoListAPI.Context;
    using Microsoft.EntityFrameworkCore;
    
    builder.Services.AddDbContext<ToDoContext>(opt =>
        opt.UseInMemoryDatabase("ToDos"));
    

Dodawanie kontrolerów

W większości przypadków kontroler będzie miał więcej niż jedną akcję. Zazwyczaj akcje Tworzenia, odczytu, aktualizacji i usuwania (CRUD). W tym samouczku utworzymy tylko dwa elementy akcji. Odczytaj cały element akcji i element akcji tworzenia, aby zademonstrować sposób ochrony punktów końcowych.

  1. Przejdź do folderu Controllers w folderze głównym projektu.

  2. Utwórz plik o nazwie ToDoListController.cs wewnątrz tego folderu. Otwórz plik, a następnie dodaj następujący kod płyty kotłowej:

    using Microsoft.AspNetCore.Authorization;
    using Microsoft.AspNetCore.Mvc;
    using Microsoft.EntityFrameworkCore;
    using Microsoft.Identity.Web;
    using Microsoft.Identity.Web.Resource;
    using ToDoListAPI.Models;
    using ToDoListAPI.Context;
    
    namespace ToDoListAPI.Controllers;
    
    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ToDoListController : ControllerBase
    {
        private readonly ToDoContext _toDoContext;
    
        public ToDoListController(ToDoContext toDoContext)
        {
            _toDoContext = toDoContext;
        }
    
        [HttpGet()]
        [RequiredScopeOrAppPermission()]
        public async Task<IActionResult> GetAsync(){...}
    
        [HttpPost]
        [RequiredScopeOrAppPermission()]
        public async Task<IActionResult> PostAsync([FromBody] ToDo toDo){...}
    
        private bool RequestCanAccessToDo(Guid userId){...}
    
        private Guid GetUserId(){...}
    
        private bool IsAppMakingRequest(){...}
    }
    

Dodawanie kodu do kontrolera

W tej sekcji dodajemy kod do utworzonych symboli zastępczych. Tutaj nie skupiamy się na tworzeniu interfejsu API, ale raczej na jego ochronie.

  1. Zaimportuj niezbędne pakiety. Pakiet Microsoft.Identity.Web to otoka biblioteki MSAL, która ułatwia na przykład obsługę logiki uwierzytelniania przez obsługę weryfikacji tokenu. Aby upewnić się, że nasze punkty końcowe wymagają autoryzacji, użyjemy wbudowanego pakietu Microsoft.AspNetCore.Authorization .

  2. Ponieważ przyznaliśmy uprawnienia do wywoływania tego interfejsu API przy użyciu delegowanych uprawnień w imieniu użytkownika lub aplikacji, gdy klient wywołuje się jako sam, a nie w imieniu użytkownika, ważne jest, aby wiedzieć, czy wywołanie jest wykonywane przez aplikację we własnym imieniu. Najprostszym sposobem na to jest ustalenie, czy token dostępu zawiera idtyp opcjonalne oświadczenie. To idtyp oświadczenie jest najprostszym sposobem dla interfejsu API w celu określenia, czy token jest tokenem aplikacji, czy aplikacją i tokenem użytkownika. Zalecamy włączenie opcjonalnego idtyp oświadczenia.

    idtyp Jeśli oświadczenie nie jest włączone, możesz użyć roles oświadczenia iscp, aby określić, czy token dostępu jest tokenem aplikacji, czy aplikacją i tokenem użytkownika. Token dostępu wystawiony przez Tożsamość zewnętrzna Microsoft Entra ma co najmniej jedno z dwóch oświadczeń. Tokeny dostępu wystawione dla użytkownika mają scp oświadczenie. Tokeny dostępu wystawione dla aplikacji mają roles oświadczenie. Tokeny dostępu zawierające oba oświadczenia są wystawiane tylko dla użytkowników, gdzie scp oświadczenie wyznacza delegowane uprawnienia, podczas gdy roles oświadczenie wyznacza rolę użytkownika. Tokeny dostępu, które nie mają żadnego z nich, nie mają być honorowane.

    private bool IsAppMakingRequest()
    {
        if (HttpContext.User.Claims.Any(c => c.Type == "idtyp"))
        {
            return HttpContext.User.Claims.Any(c => c.Type == "idtyp" && c.Value == "app");
        }
        else
        {
            return HttpContext.User.Claims.Any(c => c.Type == "roles") && !HttpContext.User.Claims.Any(c => c.Type == "scp");
        }
    }
    
  3. Dodaj funkcję pomocnika, która określa, czy wykonywane żądanie zawiera wystarczające uprawnienia do wykonania zamierzonej akcji. Sprawdź, czy jest to aplikacja wysyłająca żądanie we własnym imieniu, czy też aplikacja wykonuje wywołanie w imieniu użytkownika, który jest właścicielem danego zasobu, sprawdzając identyfikator użytkownika.

    private bool RequestCanAccessToDo(Guid userId)
        {
            return IsAppMakingRequest() || (userId == GetUserId());
        }
    
    private Guid GetUserId()
        {
            Guid userId;
            if (!Guid.TryParse(HttpContext.User.GetObjectId(), out userId))
            {
                throw new Exception("User ID is not valid.");
            }
            return userId;
        }
    
  4. Podłącz definicje uprawnień, aby chronić trasy. Chroń interfejs API, dodając [Authorize] atrybut do klasy kontrolera. Dzięki temu akcje kontrolera mogą być wywoływane tylko wtedy, gdy interfejs API jest wywoływany z autoryzowaną tożsamością. Definicje uprawnień określają, jakie rodzaje uprawnień są potrzebne do wykonania tych akcji.

    [Authorize]
    [Route("api/[controller]")]
    [ApiController]
    public class ToDoListController: ControllerBase{...}
    

    Dodaj uprawnienia do punktu końcowego GET i punktu końcowego POST. W tym celu należy użyć metody RequiredScopeOrAppPermission , która jest częścią przestrzeni nazw Microsoft.Identity.Web.Resource . Następnie przekazujesz zakresy i uprawnienia do tej metody za pomocą atrybutów RequiredScopesConfigurationKey i RequiredAppPermissionsConfigurationKey .

    [HttpGet]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Read",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Read"
    )]
    public async Task<IActionResult> GetAsync()
    {
        var toDos = await _toDoContext.ToDos!
            .Where(td => RequestCanAccessToDo(td.Owner))
            .ToListAsync();
    
        return Ok(toDos);
    }
    
    [HttpPost]
    [RequiredScopeOrAppPermission(
        RequiredScopesConfigurationKey = "AzureAD:Scopes:Write",
        RequiredAppPermissionsConfigurationKey = "AzureAD:AppPermissions:Write"
    )]
    public async Task<IActionResult> PostAsync([FromBody] ToDo toDo)
    {
        // Only let applications with global to-do access set the user ID or to-do's
        var ownerIdOfTodo = IsAppMakingRequest() ? toDo.Owner : GetUserId();
    
        var newToDo = new ToDo()
        {
            Owner = ownerIdOfTodo,
            Description = toDo.Description
        };
    
        await _toDoContext.ToDos!.AddAsync(newToDo);
        await _toDoContext.SaveChangesAsync();
    
        return Created($"/todo/{newToDo!.Id}", newToDo);
    }
    

Uruchamianie interfejsu API

Uruchom interfejs API, aby upewnić się, że działa dobrze bez żadnych błędów przy użyciu polecenia dotnet run. Jeśli zamierzasz używać protokołu HTTPS nawet podczas testowania, musisz ufać usłudze . Certyfikat dewelopera platformy NET.

Pełny przykład tego kodu interfejsu API można znaleźć w pliku przykładów.

Następny krok