教學課程:保護在客戶租使用者中註冊的 ASP.NET Web API

Web API 可能包含需要使用者驗證和授權的資訊。 應用程式可以使用委派的存取權,代表登入的使用者或僅限應用程式存取權,只有在呼叫受保護的 Web API 時,才做為應用程式自己的身分識別。

在本教學課程中,我們會建置 Web API,將委派的許可權發佈 (範圍) 和應用程式許可權 (應用程式角色) 。 用戶端應用程式,例如代表登入使用者取得權杖的 Web 應用程式會使用委派的許可權。 用戶端應用程式,例如取得權杖供自己使用的精靈應用程式會使用應用程式許可權。

在本教學課程中,您會了解如何:

設定 Web API tp 時,請使用其應用程式註冊詳細資料 設定 Web API 以使用在應用程式註冊中註冊的委派和應用程式許可權 保護您的 Web API 端點

必要條件

建立 ASP.NET Core Web API

  1. 開啟您的終端機,然後流覽至您要讓專案上線的資料夾。

  2. 執行下列命令:

    dotnet new webapi -o ToDoListAPI
    cd ToDoListAPI
    
  3. 當出現對話方塊詢問您是否要將所需的資產新增至專案時,選取 [是]

安裝套件

安裝下列套件:

  • Microsoft.EntityFrameworkCore.InMemory ,可讓 Entity Framework Core 與記憶體內部資料庫搭配使用。 它並非專為生產環境使用而設計。
  • Microsoft.Identity.Web簡化將驗證和授權支援新增至 Web 應用程式和 Web API 與 Microsoft 身分識別平臺。
dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

設定應用程式註冊詳細資料

在您的 app 資料夾中開啟 appsettings.json 檔案,並在註冊 Web 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.ReadWriteToDoList.Read 做為委派的許可權,以及 ToDoList.ReadWrite.AllToDoList.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 ASP.NET Core應用程式服務提供者中註冊名為 的 ToDoContext 子類別, (也稱為相依性插入容器) 。 內容設定為使用記憶體內部資料庫。

    // 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,而是保護 API。

  1. 匯入必要的套件。 Microsoft.Identity.Web套件是 MSAL 包裝函式,可協助我們輕鬆處理驗證邏輯,例如處理權杖驗證。 為了確保端點需要授權,我們會使用內建的 Microsoft.AspNetCore.Authorization 套件。

  2. 由於我們授與此 API 的許可權,以代表使用者使用委派的許可權,或用戶端呼叫本身而非代表使用者的應用程式許可權,因此請務必知道應用程式是否代表自己進行呼叫。 若要這樣做,最簡單的方法是尋找存取權杖是否包含選擇性宣告的 idtyp 宣告。 此 idtyp 宣告是 API 判斷權杖是應用程式權杖還是應用程式 + 使用者權杖的最簡單方式。 建議您啟用 idtyp 選擇性宣告。

    idtyp如果未啟用宣告,您可以使用 rolesscp 宣告來判斷存取權杖是應用程式權杖還是應用程式 + 使用者權杖。 Microsoft Entra 外部 ID所發行的存取權杖至少有兩個宣告的其中一個。 簽發給使用者的存取權杖具有 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. 插入您的許可權定義以保護路由。 將 屬性新增至控制器類別, [Authorize] 以保護您的 API。 這可確保只有在使用授權的身分識別呼叫 API 時,才能呼叫控制器動作。 許可權定義會定義執行這些動作所需的許可權類型。

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

    將許可權新增至 GET 所有端點和 POST 端點。 請使用屬於Microsoft.Identity.Web.Resource命名空間一部分的RequiredScopeOrAppPermission方法來執行此動作。 然後,您可以透過 RequiredScopesConfigurationKeyRequiredAppPermissionsConfigurationKey 屬性,將範圍和許可權傳遞給這個方法。

    [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 程式碼的完整範例,請參閱 範例檔案

下一步