ASP.NET Web API 中的驗證與授權
作者:Rick Anderson
您已建立 Web API,但現在您想要控制其存取權。 在此系列文章中,我們將探討一些保護 Web API 不受未經授權的使用者保護的選項。 此系列將涵蓋驗證和授權。
- 驗證是指知道使用者的身分識別。 例如,Alice 會使用她的使用者名稱和密碼登入,而伺服器會使用密碼來驗證 Alice。
- 授權正在決定是否允許使用者執行動作。 例如,Alice 有權取得資源,但無法建立資源。
本系列中的第一篇文章提供 Web API ASP.NET 中驗證和授權的一般概觀。 其他主題旨在說明 Web API 的常見驗證案例。
注意
感謝檢閱此一系列內容並提供寶貴意見反應的人:Rick Anderson、Levi Broderick、Barry Dorrans、Tom Dykstra、Hongmei Ge、David Matson、Daniel Roth、Tim Teebken。
驗證
Web API 假設驗證發生在主機中。 若是 Web 託管,主機是 IIS,它會使用 HTTP 模組進行驗證。 您可以將專案設定為使用 IIS 或 ASP.NET 內建的任何驗證模組,或撰寫您自己的 HTTP 模組來執行自訂驗證。
當主機驗證使用者時,它會建立主體,這是 IPrincipal 物件,代表執行程式碼時的安全性內容。 主機藉由設定 Thread.CurrentPrincipal,將主體附加至目前的執行緒。 主體包含相關聯的 Identity 物件,其中包含使用者的相關資訊。 如果使用者已驗證,Identity.IsAuthenticated 屬性會傳回 true。 對於匿名要求,IsAuthenticated 會傳回 false。 有關主體的詳細資訊,請參閱以角色為基礎的安全性。
驗證的 HTTP 訊息處理常式
您可以不要使用主機進行驗證,而是將驗證邏輯放入 HTTP 訊息處理常式中。 在此情況下,訊息處理常式會檢查 HTTP 要求並設定主體。
何時應該使用訊息處理常式進行驗證? 以下是一些取捨:
- HTTP 模組會看到所有通過 ASP.NET 管線的要求。 訊息處理常式只會看到路由傳送至 Web API 的要求。
- 您可以設定個別路由訊息處理常式,讓您將驗證配置套用至特定路由。
- HTTP 模組專屬於 IIS。 訊息處理常式與主機無關,因此可以搭配 Web 託管和自我託管使用。
- HTTP 模組會參與 IIS 記錄、稽核等等。
- HTTP 模組在管線中稍早執行。 如果您在訊息處理常式中處理驗證,則在處理常式執行之前,主體不會設定。 此外,當回應離開訊息處理常式時,主體會還原回先前的主體。
一般而言,如果您不需要支援自我託管,HTTP 模組是較佳的選項。 如果您需要支援自我託管,請考慮訊息處理常式。
設定主體
如果您的應用程式執行任何自訂驗證邏輯,您必須在兩個地方設定主體:
- Thread.CurrentPrincipal。 這個屬性是在 .NET 中設定執行緒主體的標準方式。
- HttpContext.Current.User。 此屬性專屬於 ASP.NET。
以下程式碼示範如何設定主體:
private void SetPrincipal(IPrincipal principal)
{
Thread.CurrentPrincipal = principal;
if (HttpContext.Current != null)
{
HttpContext.Current.User = principal;
}
}
針對 Web 託管,您必須在這兩個地方設定主體;否則,安全性內容可能會變得不一致。 不過,針對自我託管,HttpContext.Current 為 null。 為了確保程式碼與主機無關,因此,請在指派給 HttpContext.Current 之前先檢查 Null,如下所示。
授權
稍後會在管線中 (更靠近控制器的位置) 進行授權。 這可讓您在授與資源的存取權時做出更細微的選擇。
- 授權篩選器會在控制器動作之前執行。 如果未授權要求,篩選器會傳回錯誤回應,而且不會叫用動作。
- 在控制器動作中,您可以從 ApiController.User 屬性取得目前的主體。 例如,您可以根據使用者名稱篩選資源清單,只傳回屬於該使用者的資源。
使用 [授權] 屬性
Web API 提供內建的授權篩選器 AuthorizeAttribute。 此篩選器會檢查使用者是否已經過驗證。 如果沒有,它會傳回 HTTP 狀態代碼 401 (未經授權),而不叫用動作。
您可以在全域、控制器層級或個別動作層級套用篩選器。
全域:若要限制每個 Web API 控制器的存取,請將 AuthorizeAttribute 篩選器新增至全域篩選清單:
public static void Register(HttpConfiguration config)
{
config.Filters.Add(new AuthorizeAttribute());
}
控制器:若要限制特定控制器的存取,請將篩選器新增為控制器的屬性:
// Require authorization for all actions on the controller.
[Authorize]
public class ValuesController : ApiController
{
public HttpResponseMessage Get(int id) { ... }
public HttpResponseMessage Post() { ... }
}
動作:若要限制特定動作的存取,請將屬性新增至動作方法:
public class ValuesController : ApiController
{
public HttpResponseMessage Get() { ... }
// Require authorization for a specific action.
[Authorize]
public HttpResponseMessage Post() { ... }
}
或者,您可以使用 [AllowAnonymous]
屬性來限制控制器,然後允許匿名存取特定動作。 在下列範例中, Post
方法會受到限制,但 Get
方法可允許匿名存取。
[Authorize]
public class ValuesController : ApiController
{
[AllowAnonymous]
public HttpResponseMessage Get() { ... }
public HttpResponseMessage Post() { ... }
}
在上述範例中,篩選器允許任何已驗證的使用者存取受限制的方法;只有匿名使用者被排除在外。您也可以限制特定使用者或特定角色中的使用者存取:
// Restrict by user:
[Authorize(Users="Alice,Bob")]
public class ValuesController : ApiController
{
}
// Restrict by role:
[Authorize(Roles="Administrators")]
public class ValuesController : ApiController
{
}
注意
Web API 控制器的 AuthorizeAttribute 篩選器位於 System.Web.Http 命名空間中。 System.Web.Mvc 命名空間中的 MVC 控制器有類似的篩選器,與 Web API 控制器不相容。
自訂授權篩選器
若要撰寫自訂授權篩選器,請衍生自下列其中一種類型:
- AuthorizeAttribute. 擴充此類別,以根據目前使用者和使用者的角色來執行授權邏輯。
- AuthorizationFilterAttribute。 擴充此類別,以執行不一定以目前使用者或角色為基礎的同步授權邏輯。
- IAuthorizationFilter。 實作此介面以執行非同步授權邏輯;例如,如果您的授權邏輯進行非同步 I/O 或網路呼叫。 (如果您的授權邏輯是 CPU 繫結,則衍生自 會比較簡單AuthorizationFilterAttribute,因為您不需要撰寫非同步方法。)
下圖顯示 AuthorizeAttribute 類別的類別階層。
Authorize Attribute 類別的類別階層圖表。 授權屬性位於底部,箭號指向 [授權篩選屬性],而箭號指向頂端的 [I 授權篩選器]。
控制器動作內的授權
在某些情況下,您可能會允許要求繼續,但根據主體變更行為。 例如,您傳回的資訊可能會根據使用者的角色而變更。 在控制器方法中,您可以從 ApiController.User 屬性取得目前的主體。
public HttpResponseMessage Get()
{
if (User.IsInRole("Administrators"))
{
// ...
}
}