共用方式為


教學課程:使用 Microsoft 身分識別平臺建置及保護 ASP.NET Core Web API

適用於:白色勾勾符號的 綠色圓圈。 Workforce 租戶 綠色圓圈帶白色勾勾符號。 外部租戶(了解更多

本教學課程系列示範如何使用 Microsoft 身分識別平臺來保護 ASP.NET Core Web API,以限制其僅存取授權的使用者和用戶端應用程式。 您建置的 Web API 會使用委派的許可權(範圍)和應用程式許可權(應用程式角色)。

在本教學課程中,您會:

  • 建置 ASP.NET Core Web API
  • 設定 Web API 以使用其 Microsoft Entra 應用程式註冊詳細資訊
  • 保護您的 Web API 端點
  • 執行 Web API 以確保其正在接聽 HTTP 要求

先決條件

建立新的 ASP.NET Core Web API 專案

若要建立基本 ASP.NET Core Web API 專案,請遵循下列步驟:

  1. 在 Visual Studio Code 或任何其他程式碼編輯器上開啟終端機,並流覽至您要建立項目的目錄。

  2. 在 .NET CLI 或任何其他命令行工具上執行下列命令。

    dotnet new web -o TodoListApi
    cd TodoListApi
    
  3. 當對話框詢問您是否要信任作者時,請選取 [[是]

  4. 在對話框詢問是否要將必要的資產新增至專案時,選取 []。

安裝必要的套件

若要建置、保護及測試 ASP.NET Core Web API,您需要安裝下列套件:

  • Microsoft.EntityFrameworkCore.InMemory- 可讓您搭配記憶體內部資料庫使用 Entity Framework Core 的套件。 這對於測試用途很有用,但並非專為生產環境使用而設計。
  • Microsoft.Identity.Web - 一組 ASP.NET 核心連結庫,可簡化將驗證和授權支援新增至與Microsoft身分識別平臺整合的Web應用程式和Web API。

若要安裝套件,請使用:

dotnet add package Microsoft.EntityFrameworkCore.InMemory
dotnet add package Microsoft.Identity.Web

設定應用程式註冊詳細數據

在應用程式資料夾中開啟 appsettings.json 檔案,並新增您在註冊 Web API 之後所記錄的應用程式註冊詳細數據。

{
    "AzureAd": {
        "Instance": "Enter_the_Authority_URL_Here",
        "TenantId": "Enter_the_Tenant_Id_Here",
        "ClientId": "Enter_the_Application_Id_Here"
    },
    "Logging": {...},
  "AllowedHosts": "*"
}

取代下列佔位符,如下所示:

  • 以您的應用程式 (用戶端) 識別碼取代 Enter_the_Application_Id_Here
  • 使用您的目錄(租用戶)ID 替換 Enter_the_Tenant_Id_Here
  • Enter_the_Authority_URL_Here 取代為您的權威 URL,如下一節所述。

應用程式的授權URL

授權單位 URL 指定了 Microsoft 驗證程式庫(MSAL)用於請求令牌的目錄。 您會在員工團隊和外部租戶中用不同的方法建構它,如下所示:

//Instance for workforce tenant
Instance: "https://login.microsoftonline.com/"

使用自訂網址 (選擇性)

"員工租戶不支援自訂網域名稱。"

新增許可權

所有 API 都必須發布至少一個範圍,也稱為委派許可權,用戶端應用程式才能成功取得使用者的存取令牌。 API 也應該發佈至少一個應用程式角色,也稱為應用程式許可權,讓用戶端應用程式自行取得存取令牌,也就是說,當他們未登入使用者時。

我們會在 appsettings.json 檔案中指定這些許可權。 在本教學課程中,您已註冊下列委派和應用程式許可權:

  • 委派的許可權:ToDoList.ReadToDoList.ReadWrite
  • 應用程式許可權:ToDoList.Read.AllToDoList.ReadWrite.All

當使用者或用戶端應用程式呼叫 Web API 時,只有具有這些範圍或許可權的用戶端才獲得存取受保護端點的授權。

{
  "AzureAd": {
    "Instance": "Enter_the_Authority_URL_Here",
    "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": "*"
}

在 API 中實作驗證和授權

若要設定驗證和授權,請開啟 program.cs 檔案,並取代其內容下列代碼段:

新增驗證配置

在此 API 中,我們會使用 JSON Web 令牌 (JWT) 持有人配置作為預設驗證機制。 使用 AddAuthentication 方法來註冊 JWT 持有人方案。

// Add required packages to your imports
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;

var builder = WebApplication.CreateBuilder(args);

// Add an authentication scheme
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
    .AddMicrosoftIdentityWebApi(builder.Configuration);

建立應用程式的模型

在專案的根資料夾中,建立名為 Models 的資料夾。 流覽至 Models 資料夾,並建立名為 ToDo.cs 的檔案,然後新增下列程式代碼。

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;
}

上述程式代碼會建立名為 ToDo 的模型。 此模型代表應用程式管理的數據。

新增資料庫上下文

接下來,我們會定義資料庫內容類別,以協調數據模型的 Entity Framework 功能。 這個類別繼承自管理應用程式與資料庫之間互動 的 Microsoft.EntityFrameworkCore.DbContext 類別。 若要新增資料庫內容,請遵循下列步驟:

  1. 在專案的根資料夾中建立名為 DbContext 的資料夾。

  2. 瀏覽至 DbContext 資料夾,並建立名為 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 檔案,並使用下列程式代碼加以更新:

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

在上述代碼段中,我們會將 DB 內容註冊為 ASP.NET Core 應用程式服務提供者中的範圍服務(也稱為相依性插入容器)。 您也可以將 ToDoContext 類別設定為使用 ToDo 清單 API 的記憶體內部資料庫。

設定控制器

控制器通常會實作建立、讀取、更新和刪除 (CRUD) 動作來管理資源。 由於本教學課程更著重於保護 API 端點,所以我們只會在控制器中實作兩個動作專案。 提供一個 "讀取" 動作以檢索所有待辦事項,和一個 "建立" 動作以新增一個新的待辦事項。 請遵循下列步驟,將控制器新增至您的專案:

  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.NET 包裝函式,可協助我們輕鬆處理驗證邏輯,例如處理令牌驗證。 為了確保我們的端點需要授權,我們會使用內建的 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. 插入您的許可權定義來保護路由。 為了保護您的 API,請在控制器類別中新增 [Authorize] 屬性。 這可確保只有在使用授權的身分識別呼叫 API 時,才能呼叫控制器動作。 許可權定義會定義執行這些動作所需的許可權類型。

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

    將許可權新增至 GET 和 POST 端點。 使用屬於 Microsoft.Identity.Web.Resource 命名空間的 RequiredScopeOrAppPermission 方法執行此動作。 然後,您可以透過 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 中間件以使用控制器

接下來,我們會將應用程式設定為辨識並使用控制器來處理 HTTP 要求。 開啟 檔案, program.cs 並新增下列程序代碼,以在相依性插入容器中註冊控制器服務。


builder.Services.AddControllers();

var app = builder.Build();
app.MapControllers();

app.Run();

在上述代碼段中, AddControllers() 方法會藉由註冊必要的服務來準備應用程式以使用控制器,同時 MapControllers() 對應控制器路由來處理傳入的 HTTP 要求。

執行您的 API

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

  1. 在終端機中輸入下列命令以啟動應用程式:

    dotnet run
    
  2. 類似下列的輸出應該會顯示在終端機中,這可確認應用程式正在執行 http://localhost:{port} 並接聽要求。

    Building...
    info: Microsoft.Hosting.Lifetime[0]
        Now listening on: http://localhost:{port}
    info: Microsoft.Hosting.Lifetime[0]
        Application started. Press Ctrl+C to shut down.
    ...
    

網頁 http://localhost:{host} 會顯示類似下圖的輸出。 這是因為 API 是在未驗證的情況下呼叫。 若要進行授權的呼叫,請參閱 後續步驟,以取得如何存取受保護 Web API 的指引。

顯示網頁啟動時 401 錯誤的螢幕快照。

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

後續步驟