教學課程:保護在客戶租使用者中註冊的 ASP.NET Web API
Web API 可能包含需要使用者驗證和授權的資訊。 應用程式可以使用委派的存取權,代表登入的使用者或僅限應用程式存取權,只有在呼叫受保護的 Web API 時,才做為應用程式自己的身分識別。
在本教學課程中,我們會建置 Web API,將委派的許可權發佈 (範圍) 和應用程式許可權 (應用程式角色) 。 用戶端應用程式,例如代表登入使用者取得權杖的 Web 應用程式會使用委派的許可權。 用戶端應用程式,例如取得權杖供自己使用的精靈應用程式會使用應用程式許可權。
在本教學課程中,您會了解如何:
設定 Web API tp 時,請使用其應用程式註冊詳細資料 設定 Web API 以使用在應用程式註冊中註冊的委派和應用程式許可權 保護您的 Web API 端點
必要條件
公開至少一個範圍 (委派許可權的 API 註冊,) 和一個應用程式角色 (應用程式許可權) ,例如 ToDoList.Read。 如果您尚未這麼做,請遵循註冊步驟,在Microsoft Entra系統管理中心註冊 API。 請確定您有下列專案:
- Web API 的應用程式 (用戶端) 識別碼
- 已註冊 Web API 的目錄 (租使用者) 識別碼
- 目錄 (租使用者) 註冊 Web API 的子域。 例如,如果您的 主域contoso.onmicrosoft.com,則您的 Directory (租使用者) 子域為 contoso。
- ToDoList.Read 和 ToDoList.ReadWrite 作為 Web API 所公開 () 範圍的委派許可權。
- ToDoList.Read.All 和 ToDoList.ReadWrite.All 作為 應用程式許可權, (Web API 公開的應用程式角色) 。
.NET 7.0 或更新版本。
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
設定應用程式註冊詳細資料
在您的 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.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 檔案,然後在 檔案中新增下列程式碼。 此程式碼會在
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) 動作。 在本教學課程中,我們只會建立兩個動作專案。 讀取所有動作專案和建立動作專案,以示範如何保護您的端點。
流覽至專案根資料夾中的 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
執行您的 API,以確保其運作正常,而不需使用 命令 dotnet run
發生任何錯誤。 如果您想要即使在測試期間使用 HTTPs 通訊協定,則需要 信任 。NET 的開發憑證。
如需此 API 程式碼的完整範例,請參閱 範例檔案。