分享方式:


教學課程:保護已在外部租用戶中註冊的 ASP.NET Core Web API

本教學課程系列示範如何保護外部租用戶中已註冊的 Web API。 在本教學課程中,您將建置 ASP.NET Core Web API,其會發佈委派 (範圍)和應用程式權限 (應用程式角色)。

在本教學課程中:

  • 設定 Web API 以使用其應用程式註冊詳細資料
  • 設定 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

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

開啟應用程式資料夾中的 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 取代為您的目錄 (租用戶) 子網域。

使用自訂 URL 網域 (選用)

使用自訂網域對驗證 URL 進行完整品牌化。 就使用者而言,使用者在驗證過程中一直停留在您的網域中,而不會重新導向至 ciamlogin.com 網域名稱。

遵循下列步驟來使用 自訂網域:

  1. 使用針對外部租用戶中的應用程式啟用自訂 URL 網域中的步驟,為外部租用戶啟用自訂 URL 網域。

  2. 開啟 appsettings.json 檔案:

    1. Instance 屬性的值更新為 https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here。 以您的自訂 URL 網域取代 Enter_the_Custom_Domain_Here,並以您的租用戶識別碼取代 Enter_the_Tenant_ID_Here。 如果您沒有租用戶識別碼,請了解如何讀取租用戶詳細資料
    2. 新增具有值 [Enter_the_Custom_Domain_Here]knownAuthorities 屬性。

appsettings.json 檔案進行變更之後,如果您的自訂 URL 網域為 login.contoso.com,且您的租用戶識別碼為 aaaabbbb-0000-cccc-1111-dddd2222eeee,則您的檔案看起來應該類似以下程式碼片段:

{
    "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.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 檔案,然後在該檔案中新增下列程式碼。 此程式碼會將稱為 ToDoContextDbContext 子類別註冊為 ASP.NET Core 應用程式服務提供者中的範圍服務 (也稱為相依性插入容器)。 內容會設定為使用記憶體內部資料庫。

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

新增控制器

在大部分情況下,一個控制器會有一個以上的動作。 通常為 CreateReadUpdateDelete (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

使用命令 dotnet run 執行您的 API,以確保執行正常,沒有發生任何錯誤。 如果您打算即使在測試期間也要使用 HTTPS 通訊協定,則需要信任 .NET 的開發憑證

如需此 API 程式碼的完整範例,請參閱範例檔案

後續步驟