本文介紹如何使用 Microsoft.Identity.Web 實作 ASP.NET Core 中的網頁 API 授權。 你會驗證 範圍 (委派權限)和 應用程式權限 (應用程式權限),以控制對受保護資源的存取。 範例中使用 Microsoft Entra ID 作為身份提供者。
了解授權概念
本節涵蓋認證與授權的主要差異,並說明 Microsoft.Identity.Web 如何驗證存取權杖中的內容。
認證與授權
| 概念 | Purpose | Result |
|---|---|---|
| 驗證 | 驗證身份 | 失敗時返回 401 未授權 |
| 授權 | 驗證權限 | 403 若不足夠則禁止 |
什麼會被驗證
當網頁 API 收到存取權杖時,Microsoft。Identity.Web 驗證:
- 令牌簽名 ——是來自可信機構嗎?
- Token 受眾 ——它是為這個 API 設計的嗎?
- 代幣到期 ——它還有效嗎?
- 範圍/角色 ——客戶端應用程式和主體(使用者)是否擁有正確的權限?
本指南聚焦於 #4 — 驗證範圍與應用程式權限。
範圍(授權權限)
當使用者將權限委派給應用程式以代表其行動時,適用於此範圍(例如,代表已登入使用者呼叫的網頁 API)。
| 詳細資料 | 價值 |
|---|---|
| 象徵性主張 |
scp 或 scope (用戶端應用程式); roles (使用者) |
| 範例數值 |
"access_as_user"、"User.Read"、"Files.ReadWrite" |
應用程式權限(應用程式權限)
當應用程式以自身身份呼叫 Web API 並且沒有使用者上下文時,應用程式許可權會生效,例如使用用戶端憑證的背景程序或背景服務。
| 詳細資料 | 價值 |
|---|---|
| 象徵性主張 | roles |
| 範例數值 |
"Mail.Read.All"、"User.Read.All" |
使用 RequiredScope 驗證範圍
屬性 RequiredScope 檢查存取權杖是否至少包含其中一個指定的範圍。 當 API 只提供使用者委派請求時,請使用此屬性。
設定範圍驗證
請依照以下步驟在你的 API 中啟用範圍驗證。
1. 在您的 API 中啟用授權:
在您的應用程式管線中加入認證與授權服務:
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization(); // Required for authorization
var app = builder.Build();
app.UseAuthentication();
app.UseAuthorization(); // Must be after UseAuthentication
app.MapControllers();
app.Run();
2. 保護控制者或行動:
將 [Authorize] 和 [RequiredScope] 屬性套用到您的控制器或個別操作上:
using Microsoft.AspNetCore.Authorization;
using Microsoft.Identity.Web.Resource;
[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
[HttpGet]
public IActionResult GetTodos()
{
// Only accessible if token has "access_as_user" scope
return Ok(new[] { "Todo 1", "Todo 2" });
}
}
套用範圍樣式
選擇最適合你管理應用範圍的模式。
模式一:硬編碼瞄準鏡
當瞄準鏡固定且開發時已知時,請使用此模式。
[Authorize]
[RequiredScope("access_as_user")]
public class TodoListController : ControllerBase
{
// All actions require "access_as_user" scope
}
要接受多個作用域中的任一個,請將它們列為參數:
[Authorize]
[RequiredScope("read", "write", "admin")]
public class TodoListController : ControllerBase
{
// Token must have "read" OR "write" OR "admin"
}
模式二:從配置開始的示波器
當範圍應該可依環境設定時,請使用此模式。 請在你的設定檔中定義作用域:
appsettings.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-api-client-id",
"Scopes": "access_as_user read write"
}
}
參考你控制器裡的設定金鑰:
[Authorize]
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
public class TodoListController : ControllerBase
{
// Scopes read from configuration
}
這種方法讓你可以在不重新編譯的情況下更改範圍。
模式三:動作層級範圍
當不同動作需要不同權限時,請使用此模式。 應用 [RequiredScope] 於個別行動方法:
[Authorize]
public class TodoListController : ControllerBase
{
[HttpGet]
[RequiredScope("read")]
public IActionResult GetTodos()
{
return Ok(todos);
}
[HttpPost]
[RequiredScope("write")]
public IActionResult CreateTodo([FromBody] Todo todo)
{
// Only tokens with "write" scope can create
return CreatedAtAction(nameof(GetTodos), todo);
}
[HttpDelete("{id}")]
[RequiredScope("admin")]
public IActionResult DeleteTodo(int id)
{
// Only tokens with "admin" scope can delete
return NoContent();
}
}
了解驗證流程
當請求到達時,中介軟體會依以下順序處理:
- ASP.NET Core 認證中介軟體會驗證該憑證
-
RequiredScope屬性檢查用於scp或scope聲明 - 如果該標記至少包含一個匹配的範圍,請求就會繼續進行。
- 若未找到匹配的範圍,API 會回傳 403 禁止回應。
以下範例展示了典型的誤差響應:
{
"error": "insufficient_scope",
"error_description": "The token does not have the required scope 'access_as_user'."
}
用 RequiredScopeOrAppPermission 驗證應用程式權限
屬性 RequiredScopeOrAppPermission 用來驗證 範圍 (委派)或 應用程式權限 (應用程式)。 當你的 API 同時從同一端點服務使用者委派的應用程式和守護程序/服務應用程式時,請使用這個屬性。
如果你的 API 只處理使用者委派的請求,請改用 RequiredScope 。
設定範圍或應用程式權限驗證
套用屬性以接受任一標記類型:
using Microsoft.Identity.Web.Resource;
[Authorize]
[RequiredScopeOrAppPermission(
AcceptedScope = new[] { "access_as_user" },
AcceptedAppPermission = new[] { "TodoList.ReadWrite.All" }
)]
public class TodoListController : ControllerBase
{
[HttpGet]
public IActionResult GetTodos()
{
// Accessible with EITHER:
// - User-delegated token with "access_as_user" scope, OR
// - App-only token with "TodoList.ReadWrite.All" app permission
return Ok(todos);
}
}
從設定中設定應用程式權限
在設定中儲存範圍和應用程式權限,方便在不重新編譯的情況下更改它們。
appsettings.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"TenantId": "your-tenant-id",
"ClientId": "your-api-client-id",
"Scopes": "access_as_user",
"AppPermissions": "TodoList.ReadWrite.All TodoList.Admin"
}
}
參考你控制器裡的設定鍵:
[Authorize]
[RequiredScopeOrAppPermission(
RequiredScopesConfigurationKey = "AzureAd:Scopes",
RequiredAppPermissionsConfigurationKey = "AzureAd:AppPermissions"
)]
public class TodoListController : ControllerBase
{
// Scopes and app permissions from configuration
}
比較代幣權利主張的差異
下表顯示用戶委派與僅應用程式代幣間申訴的差異:
| 記號類型 | 索賠 | 範例值 |
|---|---|---|
| 使用者委派 |
scp 或 scope |
"access_as_user User.Read" |
| 僅限應用程式 | roles |
["TodoList.ReadWrite.All"] |
以下範例展示了一個由使用者委派的標記:
{
"aud": "api://your-api-client-id",
"iss": "https://login.microsoftonline.com/.../v2.0",
"scp": "access_as_user",
"sub": "user-object-id",
...
}
以下範例展示了一個僅供應用程式使用的權杖:
{
"aud": "api://your-api-client-id",
"iss": "https://login.microsoftonline.com/.../v2.0",
"roles": ["TodoList.ReadWrite.All"],
"sub": "app-object-id",
...
}
建立授權政策
對於複雜的授權情境,請使用 ASP.NET Core 授權政策。 政策讓你能集中管理規則、結合多項需求,並撰寫可測試的授權邏輯。
| 優點 | Description |
|---|---|
| 集中式邏輯 | 定義一次授權規則,然後在各處重複使用 |
| 可組合 | 結合多項需求(範圍 + 理賠 + 自訂邏輯) |
| 可測試 | 更易於單元測試授權邏輯 |
| Flexible | 範圍驗證之外的自訂需求 |
模式一:用 RequireScope 定義政策
定義需要特定範圍的具名政策,接著在控制器中引用它們。
using Microsoft.Identity.Web;
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddAuthentication(JwtBearerDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApi(builder.Configuration.GetSection("AzureAd"));
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("TodoReadPolicy", policyBuilder =>
{
policyBuilder.RequireScope("read", "access_as_user");
});
options.AddPolicy("TodoWritePolicy", policyBuilder =>
{
policyBuilder.RequireScope("write", "admin");
});
});
var app = builder.Build();
將政策套用到控制器的操作:
[Authorize]
public class TodoListController : ControllerBase
{
[HttpGet]
[Authorize(Policy = "TodoReadPolicy")]
public IActionResult GetTodos()
{
return Ok(todos);
}
[HttpPost]
[Authorize(Policy = "TodoWritePolicy")]
public IActionResult CreateTodo([FromBody] Todo todo)
{
return CreatedAtAction(nameof(GetTodos), todo);
}
}
模式二:定義具有 ScopeAuthorizationRequirement 的政策
使用 ScopeAuthorizationRequirement 以更明確地要求範圍:
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.Resource;
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("CustomPolicy", policyBuilder =>
{
policyBuilder.AddRequirements(
new ScopeAuthorizationRequirement(new[] { "access_as_user" })
);
});
});
模式三:設定預設政策
設定一個自動套用於所有 [Authorize] 屬性的預設政策:
builder.Services.AddAuthorization(options =>
{
var defaultPolicy = new AuthorizationPolicyBuilder()
.RequireScope("access_as_user")
.Build();
options.DefaultPolicy = defaultPolicy;
});
每個[Authorize]屬性現在都需要作用域:access_as_user
[Authorize] // Automatically requires "access_as_user" scope
public class TodoListController : ControllerBase
{
// All actions protected by default policy
}
模式四:結合多項需求
將範圍、角色與認證需求合併於單一政策中:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminPolicy", policyBuilder =>
{
policyBuilder.RequireScope("admin");
policyBuilder.RequireRole("Admin"); // Also check role claim
policyBuilder.RequireAuthenticatedUser();
});
});
模式五:從設定建立政策
從設定載入範圍以確保政策專屬於特定環境。
var requiredScopes = builder.Configuration["AzureAd:Scopes"]?.Split(' ');
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("ApiAccessPolicy", policyBuilder =>
{
if (requiredScopes != null)
{
policyBuilder.RequireScope(requiredScopes);
}
});
});
依租戶篩選請求
限制 API 存取權杖,僅限於特定 Microsoft Entra 租戶。 當你的多租戶 API 只接受已核准的客戶租戶請求時,這很有用。
限制允許的租戶進入
請定義一份將租戶ID申索與允許清單進行比對的政策:
builder.Services.AddAuthorization(options =>
{
string[] allowedTenants =
{
"14c2f153-90a7-4689-9db7-9543bf084dad", // Contoso tenant
"af8cc1a0-d2aa-4ca7-b829-00d361edb652", // Fabrikam tenant
"979f4440-75dc-4664-b2e1-2cafa0ac67d1" // Northwind tenant
};
options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
{
policyBuilder.RequireClaim(
"http://schemas.microsoft.com/identity/claims/tenantid",
allowedTenants
);
});
// Apply to all endpoints by default
options.DefaultPolicy = options.GetPolicy("AllowedTenantsOnly");
});
從設定中設定租戶過濾
將允許的租戶 ID 儲存在設定中,以便在不更改程式碼的情況下進行管理。
appsettings.json:
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "your-api-client-id",
"AllowedTenants": [
"14c2f153-90a7-4689-9db7-9543bf084dad",
"af8cc1a0-d2aa-4ca7-b829-00d361edb652"
]
}
}
請閱讀租戶名單並在啟動時建立政策:
var allowedTenants = builder.Configuration.GetSection("AzureAd:AllowedTenants")
.Get<string[]>();
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AllowedTenantsOnly", policyBuilder =>
{
policyBuilder.RequireClaim(
"http://schemas.microsoft.com/identity/claims/tenantid",
allowedTenants ?? Array.Empty<string>()
);
});
});
將範圍與租戶過濾結合使用
建立一份同時要求有效範圍與核准租戶的政策:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("SecureApiAccess", policyBuilder =>
{
// Require specific scope
policyBuilder.RequireScope("access_as_user");
// AND require specific tenant
policyBuilder.RequireClaim(
"http://schemas.microsoft.com/identity/claims/tenantid",
allowedTenants
);
});
});
遵循最佳做法
應用這些建議來建立安全且可維護的授權邏輯。
應該做的事
1. 務必與範圍驗證配對 [Authorize] :
[Authorize] // Authentication
[RequiredScope("access_as_user")] // Authorization
public class MyController : ControllerBase { }
2. 使用適用於特定環境的範圍設定:
[RequiredScope(RequiredScopesConfigurationKey = "AzureAd:Scopes")]
3. 適用最低特權:
[HttpGet]
[RequiredScope("read")] // Only read permission needed
[HttpPost]
[RequiredScope("write")] // Write permission for modifications
4. 使用政策進行複雜授權:
builder.Services.AddAuthorization(options =>
{
options.AddPolicy("AdminOnly", policy =>
{
policy.RequireScope("admin");
policy.RequireClaim("department", "IT");
});
});
5. 在開發過程中啟用詳細的錯誤回應:
if (builder.Environment.IsDevelopment())
{
Microsoft.IdentityModel.Logging.IdentityModelEventSource.ShowPII = true;
}
禁忌事项
1. 使用[Authorize]時不要跳RequiredScope過:
// Wrong - RequiredScope won't work without [Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }
// Correct
[Authorize]
[RequiredScope("access_as_user")]
public class MyController : ControllerBase { }
2. 不要在生產環境中硬編碼租戶 ID:
// Wrong
policyBuilder.RequireClaim("tid", "14c2f153-90a7-4689-9db7-9543bf084dad");
// Better - use configuration
var tenants = Configuration.GetSection("AllowedTenants").Get<string[]>();
policyBuilder.RequireClaim("tid", tenants);
3. 不要將範圍與角色混淆:
// Wrong - This checks roles claim, not scopes
[RequiredScope("Admin")] // "Admin" is typically a role, not a scope
// Correct
[RequiredScope("access_as_user")] // Scope
[Authorize(Roles = "Admin")] // Role
4. 避免在生產錯誤訊息中暴露敏感範圍資訊:
為生產環境設定適當的日誌等級與錯誤處理。
排除授權問題
請依照以下指引診斷常見的授權問題。
403 禁忌 - 缺少瞄準鏡
錯誤: API 即使有有效憑證,也會回傳 403。
诊断:
- 在 jwt.ms 解碼代幣。
- 請檢查
scp或scope理賠。 - 確認數值符合你的
RequiredScope屬性。
Solution:
- 確保客戶端應用程式在取得令牌時請求正確的授權範圍。
- 確認 Scope 是否在 Microsoft Entra 的 API 應用程式註冊中公開。
- 如有需要,請給予管理員同意。
RequiredScope 無法運作
症狀: 這個屬性似乎被忽略了。
檢查:
- 你有加屬性嗎
[Authorize]? -
app.UseAuthorization()之後有呼叫app.UseAuthentication()嗎? -
services.AddAuthorization()是否已註冊?
未找到設定鍵
錯誤: 範圍驗證無聲地失敗。
檢查:
{
"AzureAd": {
"Scopes": "access_as_user" // Matches RequiredScopesConfigurationKey
}
}
確保設定路徑完全一致。