Руководство. Защита веб-API ASP.NET, зарегистрированного в клиенте клиента

Веб-API могут содержать сведения, требующие проверки подлинности и авторизации пользователя. Приложения могут использовать делегированный доступ, действуя от имени вошедшего пользователя или доступ только для приложений, выступая только в качестве собственного удостоверения приложения при вызове защищенных веб-API.

В этом руководстве мы создадим веб-API, который публикует как делегированные разрешения (области), так и разрешения приложений (роли приложения). Клиентские приложения, такие как веб-приложения, которые получают маркеры от имени вошедшего пользователя, используют делегированные разрешения. Клиентские приложения, такие как управляющие программы, которые получают маркеры для себя, используют разрешения приложения.

В этом руководстве описано следующее:

Настройка веб-API tp с помощью сведений о регистрации приложения Настройка веб-API для использования делегированных разрешений и разрешений приложений, зарегистрированных в регистрации приложения Защита конечных точек веб-API

Предварительные требования

Создание веб-API ASP.NET Core

  1. Откройте терминал, а затем перейдите в папку, в которой будет работать проект.

  2. Выполните следующие команды:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. При появлении диалогового окна с запросом на добавление в проект необходимых ресурсов выберите Да.

Установка пакетов

Установите следующие пакеты:

  • Microsoft.EntityFrameworkCore.InMemory позволяет использовать Entity Framework Core с базой данных в памяти. Он не предназначен для использования в рабочей среде.
  • Microsoft.Identity.Webупрощает добавление поддержки проверки подлинности и авторизации для веб-приложений и веб-API, интегрирующихся с платформа удостоверений Майкрософт.
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

Настройка сведений о регистрации приложения

Откройте файл appsettings.json в папке приложения и добавьте сведения о регистрации приложения, записанные после регистрации веб-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": "*"
}

Замените следующие заполнители, как показано ниже:

  • Замените Enter_the_Application_Id_Here идентификатором приложения (клиента).
  • Замените Enter_the_Tenant_Id_Here идентификатором каталога (клиента).
  • Замените Enter_the_Tenant_Subdomain_Here поддоменом каталога (клиента).

Добавление роли приложения и область

Чтобы клиентские приложения успешно получили маркер доступа для пользователя, все API-интерфейсы должны публиковать не менее одного область, также называемого делегированным разрешением. API-интерфейсы также должны публиковать как минимум одну роль приложения для приложений, также называемую разрешением приложения, чтобы клиентские приложения получили маркер доступа как сами, то есть, когда они не выполняют вход пользователя.

Мы указываем эти разрешения в файле appsettings.json . В этом руководстве мы зарегистрировали четыре разрешения. ToDoList.ReadWrite и ToDoList.Read в качестве делегированных разрешений, и ToDoList.ReadWrite.All и ToDoList.Read.All в качестве разрешений приложения.

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

Добавление схемы проверки подлинности

Схема проверки подлинности называется при настройке службы проверки подлинности во время проверки подлинности. В этой статье мы используем схему проверки подлинности носителя JWT. Добавьте следующий код в файл Programs.cs , чтобы добавить схему проверки подлинности.

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

Создание моделей

Создайте папку с именем Models в корневой папке проекта. Перейдите в папку и создайте файл с именем ToDo.cs , а затем добавьте следующий код. Этот код создает модель с именем 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;
}

Добавление контекста базы данных

Контекст базы данных —это основной класс, который координирует функциональные возможности Entity Framework для модели данных. Этот класс создается путем наследования от класса Microsoft.EntityFrameworkCore.DbContext . В этом руководстве мы используем базу данных в памяти для тестирования.

  1. Создайте папку с именем DbContext в корневой папке проекта.

  2. Перейдите в папку и создайте файл с именем ToDoContext.cs , а затем добавьте в него следующее содержимое:

    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. Откройте файл Program.cs в корневой папке приложения, а затем добавьте в файл следующий код. Этот код регистрирует DbContext подкласс с именем ToDoContext в качестве службы с заданной областью в поставщике службы приложений ASP.NET Core (также известном как контейнер внедрения зависимостей). Контекст настраивается для использования базы данных в памяти.

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

Добавление контроллеров

В большинстве случаев у контроллера будет несколько действий. Обычно это действия создания, чтения, обновления и удаления (CRUD). В этом руководстве мы создадим только два элемента действия. Элемент действия read all и create action item для демонстрации защиты конечных точек.

  1. Перейдите в папку Контроллеры в корневой папке проекта.

  2. Создайте файл с именем ToDoListController.cs в этой папке. Откройте файл и добавьте следующий код плиты:

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

Добавление кода в контроллер

В этом разделе мы добавим код в созданные заполнители. Здесь основное внимание уделяется не созданию API, а его защите.

  1. Импортируйте необходимые пакеты. Пакет Microsoft.Identity.Web — это оболочка MSAL, которая помогает легко обрабатывать логику проверки подлинности, например, с помощью проверки маркеров. Чтобы убедиться, что для конечных точек требуется авторизация, мы используем встроенный пакет Microsoft.AspNetCore.Authorization .

  2. Так как мы предоставили разрешения для вызова этого API с использованием делегированных разрешений от имени пользователя или разрешений приложения, когда клиент вызывает как себя, а не от имени пользователя, важно знать, выполняется ли вызов приложением от его имени. Самый простой способ сделать это — это утверждения, чтобы определить, содержит ли маркер доступа необязательное idtyp утверждение. Это idtyp утверждение является самым простым для API способом определить, является ли маркер маркером приложения или маркером приложения и пользователя. Рекомендуется включить необязательное idtyp утверждение.

    idtyp Если утверждение не включено, можно использовать roles утверждения и scp , чтобы определить, является ли маркер доступа маркером приложения или маркером приложения и пользователя. Маркер доступа, выданный Внешняя идентификация Microsoft Entra, содержит по крайней мере одно из двух утверждений. Маркеры доступа, выданные пользователю, имеют scp утверждение . Маркеры доступа, выданные приложению, имеют roles утверждение . Маркеры доступа, содержащие оба утверждения, выдаются только пользователям, где scp утверждение обозначает делегированные разрешения, а roles утверждение — роль пользователя. Маркеры доступа, которые не имеют ни, не должны учитываться.

    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. Добавьте вспомогающую функцию, которая определяет, содержит ли выполняемый запрос достаточные разрешения для выполнения предполагаемого действия. Проверьте, выполняет ли это приложение запрос от своего имени или выполняет ли приложение вызов от имени пользователя, которому принадлежит данный ресурс, путем проверки идентификатора пользователя.

    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. Подключите определения разрешений для защиты маршрутов. Защитите API, [Authorize] добавив атрибут в класс контроллера. Это гарантирует, что действия контроллера могут вызываться только в том случае, если API вызывается с авторизованным удостоверением. Определения разрешений определяют, какие типы разрешений необходимы для выполнения этих действий.

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

    Добавьте разрешения для конечной точки GET all и конечной точки POST. Для этого используется метод RequiredScopeOrAppPermission , который является частью пространства имен Microsoft.Identity.Web.Resource . Затем вы передаете области и разрешения этому методу с помощью атрибутов RequiredScopesConfigurationKey и 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);
    }
    

Запуск API

Запустите API, чтобы убедиться, что он работает без ошибок, используя команду dotnet run. Если вы планируете использовать протокол HTTPS даже во время тестирования, необходимо доверять . Сертификат разработки NET.

Полный пример этого кода API см. в файле примеров.

Дальнейшие действия