教學課程:保護已在外部租用戶中註冊的 ASP.NET Core Web API
本教學課程系列示範如何保護外部租用戶中已註冊的 Web API。 在本教學課程中,您將建置 ASP.NET Core Web API,其會發佈委派 (範圍)和應用程式權限 (應用程式角色)。
在本教學課程中:
- 設定 Web API 以使用其應用程式註冊詳細資料
- 設定 Web API 以使用應用程式註冊中註冊的委派和應用程式權限
- 保護 Web API 端點
必要條件
公開至少一個範圍 (委派權限) 和一個應用程式角色 (應用程式權限) 的 API 註冊,例如 ToDoList.Read。 如果您還没有,請遵循註冊步驟,在 Microsoft Entra 系統管理中心註冊 API。 請確定您具有下列項目:
- Web API 的應用程式 (用戶端) 識別碼
- Web API 的目錄 (租用戶) 識別碼已註冊
- Web API 註冊所在的目錄 (租用戶) 子網域。 例如,如果您的主要網域 是 contoso.onmicrosoft.com,則您的目錄 (租用戶) 子網域是 contoso。
- ToDoList.Read 和 ToDoList.ReadWrite,作為 Web API 公開的委派權限 (範圍)。
- ToDoList.Read.All 和 ToDoList.ReadWrite.All,作為 Web API 公開的應用程式權限 (應用程式角色)。
.NET 7.0 SDK 或更新版本。
Visual Studio Code 或其他程式碼編輯器。
建立 ASP.NET Core Web API
開啟您的終端機,然後瀏覽至您要讓專案位於其中的資料夾。
執行下列命令:
dotnet new webapi -o ToDoListAPI cd ToDoListAPI
當出現對話方塊詢問您是否要將所需的資產新增至專案時,選取 [是]。
安裝套件
安裝下列套件:
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 網域名稱。
遵循下列步驟來使用 自訂網域:
使用針對外部租用戶中的應用程式啟用自訂 URL 網域中的步驟,為外部租用戶啟用自訂 URL 網域。
開啟 appsettings.json 檔案:
- 將
Instance
屬性的值更新為 https://Enter_the_Custom_Domain_Here/Enter_the_Tenant_ID_Here。 以您的自訂 URL 網域取代Enter_the_Custom_Domain_Here
,並以您的租用戶識別碼取代Enter_the_Tenant_ID_Here
。 如果您沒有租用戶識別碼,請了解如何讀取租用戶詳細資料。 - 新增具有值 [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.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 類別所建立。 在本教學課程中,我們會基於測試目的使用記憶體內部資料庫。
在專案的根資料夾中,建立稱為 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; } }
在應用程式的根資料夾中開啟 Program.cs 檔案,然後在該檔案中新增下列程式碼。 此程式碼會將稱為
ToDoContext
的DbContext
子類別註冊為 ASP.NET Core 應用程式服務提供者中的範圍服務 (也稱為相依性插入容器)。 內容會設定為使用記憶體內部資料庫。// Add the following to your imports using ToDoListAPI.Context; using Microsoft.EntityFrameworkCore; builder.Services.AddDbContext<ToDoContext>(opt => opt.UseInMemoryDatabase("ToDos"));
新增控制器
在大部分情況下,一個控制器會有一個以上的動作。 通常為 Create、Read、Update 和 Delete (CRUD) 動作。 在本教學課程中,我們只會建立兩個動作項目。 一個全部讀取動作項目和一個建立動作項目,以示範如何保護您的端點。
瀏覽至專案根資料夾中的 Controllers 資料夾。
在此資料夾內建立稱為 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。
匯入必要的套件。 Microsoft.Identity.Web 套件是 MSAL 包裝函式,其可協助我們輕鬆地處理驗證邏輯,例如透過處理權杖驗證。 為了確保我們的端點需要授權,我們會使用內建的 Microsoft.AspNetCore.Authorization 套件。
由於我們已授與此 API 的權限,以代表使用者使用委派權限進行呼叫,或使用應用程式權限進行呼叫,其中用戶端以本身且不代表使用者進行呼叫,因此請務必知道應用程式是否代表自己進行呼叫。 若要這樣做,最簡單的方式就是宣告,以查明存取權杖是否包含
idtyp
選用宣告。 此idtyp
宣告是 API 判斷權杖是應用程式權杖還是應用程式 + 使用者權杖的最簡單方式。 建議您啟用idtyp
選用宣告。如果
idtyp
宣告未啟用,您可以使用roles
和scp
宣告,來判斷存取權杖是應用程式權杖還是應用程式 + 使用者權杖。 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"); } }
新增協助程式函式,其會判斷提出的要求是否包含足夠的權限來實施預期的動作。 檢查應用程式是否代表自己提出要求,或應用程式是否代表擁有指定資源的用戶進行呼叫,方法是驗證使用者識別碼。
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; }
插入您的權限定義來保護路由。 將
[Authorize]
屬性新增至控制器類別,來保護您的 API。 這可確保只有在使用已授權的身分識別呼叫 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
使用命令 dotnet run
執行您的 API,以確保執行正常,沒有發生任何錯誤。 如果您打算即使在測試期間也要使用 HTTPS 通訊協定,則需要信任 .NET 的開發憑證。
如需此 API 程式碼的完整範例,請參閱範例檔案。