Поделиться через


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

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

В этом руководстве;

  • Настройка веб-API для использования сведений о регистрации приложения
  • Настройка веб-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 поддомен каталога (клиента).

Использование личного домена URL-адреса (необязательно)

Используйте личный домен для полной фирменной символики URL-адреса проверки подлинности. С точки зрения пользователя пользователи остаются в домене во время проверки подлинности, а не перенаправляются на ciamlogin.com доменное имя.

Выполните следующие действия, чтобы использовать личный домен:

  1. Выполните действия, описанные в разделе "Включение пользовательских доменов URL-адресов" для приложений во внешних клиентах , чтобы включить личный ДОМЕН URL-адресов для внешнего клиента.

  2. Откройте файл appsettings.json :

    1. Измените значение Instance свойства https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Hereна . Замените Enter_the_Custom_Domain_Here домен личного URL-адреса и Enter_the_Tenant_ID_Here идентификатором клиента. Если у вас нет идентификатора клиента, узнайте, как прочитать сведения о клиенте.
    2. Добавьте knownAuthorities свойство со значением [Enter_the_Custom_Domain_Here].

После внесения изменений в файл appsettings.json, если личный домен URL-адреса login.contoso.com, а идентификатор клиента — aaaabbbb-0000-cccc-1111-dd222eeee, файл должен выглядеть следующим фрагментом кода:

{
    "AzureAd": {
        "Instance": "https://login.contoso.com/aaaabbbb-0000-cccc-1111-dddd2222eeee",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here",
        "KnownAuthorities": ["login.contoso.com"]
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

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

Все 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). В этом руководстве мы создадим только два элемента действия. Прочтите все элементы действия и элемент создания действия, чтобы продемонстрировать, как защитить конечные точки.

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

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

    Добавьте разрешения на получение всех конечных точек и конечной точки 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 см. в файле примеров.

Следующий шаг