Kurz: Zabezpečení webového rozhraní API ASP.NET zaregistrovaného v tenantovi zákazníka

Webová rozhraní API můžou obsahovat informace, které vyžadují ověření a autorizaci uživatelů. Aplikace můžou používat delegovaný přístup, jednat jménem přihlášeného uživatele nebo přístup jenom pro aplikace a při volání chráněných webových rozhraní API jednat pouze jako vlastní identita aplikace.

V tomto kurzu vytvoříme webové rozhraní API, které publikuje delegovaná oprávnění (obory) i oprávnění aplikací (aplikační role). Klientské aplikace, jako jsou webové aplikace, které získávají tokeny jménem přihlášeného uživatele, používají delegovaná oprávnění. Klientské aplikace, jako jsou aplikace démona, které získávají tokeny pro sebe, používají oprávnění aplikace.

V tomto kurzu se naučíte:

Konfigurace webového rozhraní API tp s použitím podrobností o registraci aplikace: Nakonfigurujte webové rozhraní API tak, aby používalo delegovaná oprávnění a oprávnění aplikací zaregistrovaná v registraci aplikace Ochrana koncových bodů webového rozhraní API

Požadavky

Vytvoření webového rozhraní API ASP.NET Core

  1. Otevřete terminál a pak přejděte do složky, ve které má být projekt aktivní.

  2. Spusťte následující příkazy:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. Když se v dialogovém okně zobrazí dotaz, jestli chcete do projektu přidat požadované prostředky, vyberte Ano.

Instalace balíčků

Nainstalujte následující balíčky:

  • Microsoft.EntityFrameworkCore.InMemory , který umožňuje použití Entity Framework Core s databází v paměti. Není určen pro produkční použití.
  • Microsoft.Identity.Webzjednodušuje přidávání podpory ověřování a autorizace do webových aplikací a webových rozhraní API integrovaných s Microsoft identity platform.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Konfigurace podrobností o registraci aplikace

Otevřete soubor appsettings.json ve složce aplikace a přidejte podrobnosti o registraci aplikace, které jste zaznamenali po registraci webového rozhraní 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": "*"
}

Nahraďte následující zástupné symboly, jak je znázorněno na obrázku:

  • Nahraďte Enter_the_Application_Id_Here ID aplikace (klienta).
  • Nahraďte Enter_the_Tenant_Id_Here id vašeho adresáře (tenanta).
  • Nahraďte Enter_the_Tenant_Subdomain_Here subdoménou adresáře (tenanta).

Přidání role a oboru aplikace

Všechna rozhraní API musí publikovat minimálně jeden obor, označovaný také jako delegovaná oprávnění, aby klientské aplikace úspěšně získaly přístupový token pro uživatele. Rozhraní API by také měla publikovat minimálně jednu roli aplikace pro aplikace, označovanou také jako oprávnění aplikace, aby klientské aplikace získaly přístupový token jako samy, tj. když se nepřihlašují k uživateli.

Tato oprávnění zadáváme v souboru appsettings.json . V tomto kurzu jsme zaregistrovali čtyři oprávnění. ToDoList.ReadWrite a ToDoList.Read jako delegovaná oprávnění a ToDoList.ReadWrite.All a ToDoList.Read.All jako oprávnění aplikace.

{
  "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": "*"
}

Přidání schématu ověřování

Schéma ověřování je pojmenováno, když je během ověřování nakonfigurovaná ověřovací služba. V tomto článku použijeme schéma ověřování nosné verze JWT. Přidejte do souboru Programs.cs následující kód, který přidá schéma ověřování.

// 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);

Vytvoření modelů

V kořenové složce projektu vytvořte složku s názvem Modely . Přejděte do složky a vytvořte soubor s názvem ToDo.cs a přidejte následující kód. Tento kód vytvoří model s názvem 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;
}

Přidání kontextu databáze

Kontext databáze je hlavní třída, která koordinuje funkce Entity Frameworku pro datový model. Tato třída je vytvořena odvozením z Microsoft.EntityFrameworkCore.DbContext třídy. V tomto kurzu používáme databázi v paměti pro účely testování.

  1. V kořenové složce projektu vytvořte složku s názvem DbContext .

  2. Přejděte do této složky a vytvořte soubor s názvem ToDoContext.cs a přidejte do něj následující obsah:

    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. Otevřete soubor Program.cs v kořenové složce aplikace a přidejte do souboru následující kód. Tento kód zaregistruje podtřídu nazvanou DbContextToDoContext jako vymezenou službu v ASP.NET Core zprostředkovateli aplikačních služeb (označuje se také jako kontejner injektáže závislostí). Kontext je nakonfigurovaný tak, aby používal databázi v paměti.

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

Přidání kontrolerů

Ve většině případů by kontroler měl více než jednu akci. Obvykle se jedná o akce CRUD (Vytvořit, Číst, Aktualizovat a Odstranit ). V tomto kurzu vytvoříme pouze dvě položky akce. Položka akce pro čtení a položka akce vytvoření, která ukazuje, jak chránit koncové body.

  1. V kořenové složce projektu přejděte do složky Kontrolery .

  2. V této složce vytvořte soubor s názvem ToDoListController.cs . Otevřete soubor a přidejte následující kód kotlové desky:

    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(){...}
    }
    

Přidání kódu do kontroleru

V této části přidáme kód do zástupných symbolů, které jsme vytvořili. Zde se nezaměřuje na sestavení rozhraní API, ale spíše na jeho ochranu.

  1. Importujte potřebné balíčky. Balíček Microsoft.Identity.Web je obálka MSAL, která nám pomáhá snadno zpracovávat logiku ověřování, například zpracováním ověřování tokenů. Abychom zajistili, že naše koncové body vyžadují autorizaci, používáme balíček Microsoft.AspNetCore.Authorization .

  2. Vzhledem k tomu, že jsme tomuto rozhraní API udělili oprávnění k volání pomocí delegovaných oprávnění jménem uživatele nebo oprávnění aplikace, kde klient volá sám, a ne jménem uživatele, je důležité vědět, jestli volání provádí aplikace jejím vlastním jménem. Nejjednodušším způsobem, jak to udělat, jsou deklarace identity, které zjistí, jestli přístupový token obsahuje volitelnou idtyp deklaraci identity. Tato idtyp deklarace identity představuje nejjednodušší způsob, jak rozhraní API určit, jestli je token tokenem aplikace nebo tokenem aplikace a uživatele. Doporučujeme povolit volitelnou idtyp deklaraci identity.

    idtyp Pokud deklarace identity není povolená, můžete pomocí roles deklarací identity a scp určit, jestli je přístupový token tokenem aplikace nebo tokenem aplikace a uživatele. Přístupový token vydaný Microsoft Entra Externí ID má alespoň jednu ze dvou deklarací identity. Přístupové tokeny vydané uživateli mají scp deklaraci identity. Přístupové tokeny vydané aplikaci mají roles deklaraci identity. Přístupové tokeny, které obsahují obě deklarace identity, se vydávají jenom uživatelům, kde scp deklarace identity určuje delegovaná oprávnění, zatímco roles deklarace identity určuje roli uživatele. Přístupové tokeny, které nemají ani jeden, se nebudou respektovat.

    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. Přidejte pomocnou funkci, která určuje, jestli vytvářená žádost obsahuje dostatečná oprávnění k provedení zamýšlené akce. Ověřením ID uživatele zkontrolujte, jestli aplikace provádí žádost vlastním jménem, nebo jestli aplikace provádí volání jménem uživatele, který daný prostředek vlastní.

    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. Připojte definice oprávnění k ochraně tras. Chraňte své rozhraní API přidáním atributu [Authorize] do třídy kontroleru. Tím se zajistí, že se akce kontroleru dají volat jenom v případě, že se rozhraní API volá s autorizovanou identitou. Definice oprávnění definují, jaké druhy oprávnění jsou potřeba k provedení těchto akcí.

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

    Přidejte oprávnění ke koncovému bodu GET all a koncovému bodu POST. Použijte metodu RequiredScopeOrAppPermission , která je součástí oboru názvů Microsoft.Identity.Web.Resource . Pak této metodě předáte obory a oprávnění prostřednictvím atributů RequiredScopesConfigurationKey a 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);
    }
    

Spuštění rozhraní API

Spusťte rozhraní API, abyste se ujistili, že funguje správně bez chyb, pomocí příkazu dotnet run. Pokud máte v úmyslu používat protokol HTTPS i během testování, musíte důvěřovat . Vývojový certifikát pro NET.

Úplný příklad tohoto kódu rozhraní API najdete v ukázkovém souboru.

Další kroky