標籤:
原文:https://msdn.microsoft.com/zh-cn/magazine/dn781361.aspx
身分識別驗證和授權是應用程式安全的基礎。身分識別驗證通過驗證提供的憑據來確定使用者身份,而授權則決定是否允許使用者執行請求的操作。安全的 Web API 身分識別驗證基於確定的身份請求和授權使用者請求的資源訪問。
您可以在 ASP.NET Web API 中使用 ASP.NET Web API 管道中提供的擴充點,以及使用由主機提供的選項來實現身分識別驗證。對於 ASP.NET Web API 的第一個版本,常見的做法是使用授權篩選器或操作篩選器來實現身分識別驗證。ASP.NET Web API 2 引入了一個專門用於此過程的新的身分識別驗證篩選器。這種新的擴充點使身分識別驗證和授權問題被清晰地劃分開。在本文中,我會向您介紹這兩種安全性篩選器,並將身分識別驗證和授權作為 ASP.NET Web API 中獨立的兩個方面,向您示範如何使用它們來實現將身分識別驗證和授權。
實現安全性方面的選項
通過使用由主機提供的擴充點以及由 ASP.NET Web API 管道自己提供的擴充點能夠實現 ASP.NET Web API 中的身分識別驗證和授權。基於主機的選項包括 HTTP 模組和 OWIN 中介軟體組件,而 ASP.NET Web API 的擴充選項包括訊息處理常式、操作篩選器、授權篩選器以及身分識別驗證篩選器。
基於主機的選項很好地整合到主機管道中,並能較早拒絕管道中的無效請求。另一方面,ASP.NET Web API 的擴充選項對身分識別驗證過程提供更精細的控制水平。也就是說,您可以對不同的控制器甚至不同的操作方法設定不同的身分識別驗證機制。權衡與主機更好地整合在一起,並較早對不佳的身分識別驗證粒度請求予以拒絕。除了這些常規特性,每個選項都有自己的優缺點,我將在後面的章節中進行介紹。
HTTP 模組這是在 IIS 上啟動並執行一個 Web API 選項。作為 IIS 管道的一部分,HTTP 模組允許較早地執行安全的程式碼。從 HTTP 模組中建立的主體適用於所有的組件,包括管道中稍後啟動並執行 IIS 組件。例如,如果主體是由響應 AuthenticateRequest 事件的 HTTP 模組構建的,則主體的使用者名稱將被正確地記錄在 IIS 日誌的 cs-username 欄位中。HTTP 模組的最大缺點是缺乏粒度。HTTP 模組對進入應用程式的所有請求做出運行反應。對於具有不同功能(如 HTML 標籤產生,Web API 等等)的 Web 應用程式,讓一個 HTTP 模組以某種方式強制執行身分識別驗證通常不是一個很靈活的方法。在這種情況下,另一個使用 HTTP 模組的缺點就顯現出來了,即依賴主機—IIS。
OWIN 中介軟體這是另一個與主機相關的選項,適用於 OWIN 主機。ASP.NET Web API 2 完全支援 OWIN。使用 OWIN 中介軟體確保安全的可能最令人信服的理由是同一中介軟體可以在不同的架構中工作。這意味著您可以將多個架構(如 ASP.NET Web API、SignalR 等)用在您的應用程式中,卻可以使用共同的安全中介軟體。然而,OWIN 中介軟體的最小粒度卻可能是一個缺點,因為 OWIN 中介軟體在 OWIN 管道中運行並且通常在處理各個請求時被調用。此外,OWIN 中介軟體只能用於與 OWIN 相容的主機,雖然這種依賴比起依賴特定的主機/伺服器(如 IIS)相對要好些,但這是 HTTP 模組的實際情況。值得注意的一點是,正是由於 Microsoft.Owin.Host.SystemWeb 包,OWIN 中介軟體才可以在(整合了 IIS 的)ASP.NET 管道中運行。
訊息處理常式由 ASP.NET Web API 提供的擴充選項,將使用訊息處理常式確保安全的最大好處就是它作為 ASP.NET Web API 架構的概念可以不依賴底層的主機或伺服器。此外,訊息處理常式僅對 Web API 請求運行。使用訊息處理常式的不足之處在於缺乏更精細的控制。可將訊息處理常式配置為對所有請求或對特定路由以全域處理常式來運行。對於給定的路由,您可以有多個控制器。所有這些控制器和它們所包含的操作方法都必須共用相同的由為此路由配置的訊息處理常式強制執行的身分識別驗證。換句話說,由訊息處理常式執行的身分識別驗證的最低粒度是在路由層級。
操作篩選器由 ASP.NET Web API 提供的另一個擴充選項是操作篩選器。然而,從執行身分識別驗證的角度來看,它不是一個可行的選擇,僅僅是因為它在授權篩選器在 ASP.NET Web API 管道運行之後才開始運行。為了讓身分識別驗證和授權能正常工作,身分識別驗證必須先於授權而運行。
授權篩選器然而,由 ASP.NET Web API 提供的另一個擴充選項是授權篩選器。對於要求比訊息處理常式能提供的更高的粒度的情形,執行自訂身分識別驗證最常見的一種方式是使用授權篩選器。將授權篩選器用於身分識別驗證和授權的主要問題是,ASP.NET Web API 並不保證身分識別驗證篩選器的執行順序。基本上,這意味著在執行身分識別驗證的授權篩選器運行之前,執行授權的授權篩選器就可以正常運行了,從而使得授權篩選器選項如同操作篩選器選項一樣不適合身分識別驗證。
身分識別驗證篩選器這是本文的重點所在,它是可用於 ASP.NET Web API 2 的最新擴充選項。身分識別驗證篩選器在訊息處理常式之後運行,並且是在其他所有篩選器類型之前運行。因此,它們是實現身分識別驗證相關操作的更好選擇。最重要的是,身分識別驗證篩選器是在授權篩選器之前啟動並執行。通過使用專門針對身分識別驗證或授權的篩選器,可以分別處理身分識別驗證和授權相關的問題。
此外,身分識別驗證篩選器提供控制或粒度層級,因此特別有用。以旨在被本機行動裝置 App程式和基於瀏覽器的 AJAX 應用程式所使用的 Web API 為例。行動裝置 App程式可能會在 HTTP Authorization 標題中顯示一個令牌,而 AJAX 應用程式可能將身分識別驗證 Cookie 用作憑據。此外,假設 API 的子集是敏感的,且僅適用於本機行動裝置 App程式,您要確保只能通過提供令牌,而不是提供 Cookie 的方式來訪問操作方法(Cookie 很容易受到跨網站請求偽造 [XSRF] 的影響,而在 HTTP Authorization 標題中的令牌則不會)。在這種情況下,身分識別驗證必須以比基於主機的選項,甚至是訊息處理常式更精細的粒度層級進行。身分識別驗證篩選器非常適合這個用例。您可以應用基於所有這些控制器的令牌上的身分識別驗證篩選器或必須使用的操作方法,以及基於其他地方的 Cookie 的身分識別驗證篩選器。假設,在這種情況下,您有一些常見的操作方法,想通過令牌或 Cookie 的方式來訪問它們。您可以將 Cookie 和令牌身分識別驗證篩選器均應用在這些常見的操作方法上,總會有一個篩選器能夠成功進行身分識別驗證。這種控制是能被推上檯面的具有最大價值的身分識別驗證篩選器。當需要精確控制身分識別驗證時,正確的做法是,通過身分識別驗證篩選器解決身分識別驗證相關問題以及通過授權篩選器解決授權相關問題。
值得一提的是,開箱即用的身分識別驗證篩選器 (HostAuthenticationFilter) 通過 OWIN 中介軟體啟用了 ASP.NET Web API 身分識別驗證。當 OWIN 身分識別驗證中介軟體在管道中運行,並試圖“主動”身分識別驗證傳入的請求時,如需要,也可以將它配置為“被動”身分識別驗證傳入的請求。HostAuthenticationFilter 允許依據 Web API 管道中後來的名稱運行被動 OWIN 身分識別驗證中介軟體。這種方法啟用了能夠在多架構間共用的身分識別驗證代碼(包括 Microsoft 提供的 OWIN 身分識別驗證中介軟體),同時仍允許將每個操作粒度用於身分識別驗證。
雖然您可以混合使用主機分級的身分識別驗證和基於更細粒度 Web API 管道的身分識別驗證,但是也必須仔細考慮主機分級的身分識別驗證會怎樣影響 Web API 身分識別驗證。例如,您可以使基於 Cookie 的身分識別驗證中介軟體處於主機分級,這意味著可以同其他架構配合使用,比如 ASP.NET MVC,但是讓 Web API 使用基於 Cookie 的主體會使得它容易受到(比如 XSRF 的)攻擊。為了協助處理這種情況,SuppressDefaultHostAuthentication 擴充方法使 Web API 忽略在主機分級配置的任何身分識別驗證。預設的 Web API Visual Studio 模板在主機分級下啟用了 Cookie,並使用在 Web API 層級的承載令牌。因為 Cookie 是在主機分級下啟用的,並要求 XSRF 緩解,所以模板還使用 SuppressDefaultHostAuthentication 阻止 Web API 管線使用基於 Cookie 的主體。這樣一來,Web API 將只使用基於令牌的主體,您則不需要為 Web API 建立抵禦 XSRF 攻擊的機制。
使身分識別驗證篩選器和授權篩選器協同工作
在 ASP.NET Web API 管道中,身分識別驗證篩選器第一個運行(緊接著啟動並執行是授權篩選器)的原因很簡單,因為授權取決於確定的身份,而這正是身分識別驗證的結果。以下為您介紹如何設計身分識別驗證篩選器和授權篩選器以協同工作來保護 ASP.NET Web API。
設計的基本原則是讓身分識別驗證篩選器只負責驗證憑據,而不是讓其處理其他問題。例如,如果未提供憑據,身分識別驗證篩選器將不會拒絕有 401 未經授權狀態碼的請求。它根本沒有確定一個經過身分識別驗證的身份,並將如何處理匿名請求的問題留給了授權階段。身分識別驗證篩選器基本執行三種類型的操作:
- 如果感興趣的憑據不存在於該請求中,則篩選器就不執行任何操作。
- 如果存在憑據且該憑據是有效,則篩選器會以經過身分識別驗證的主體的形式確定一個身份。
- 如果存在憑據但該憑據是無效的,篩選器就會通過設定一個錯誤的結果通知 ASP.NET Web API 架構,這基本上可導致向要求者發回一個“未經授權”的響應。
如果管道中啟動並執行身分識別驗證篩選器都無法檢測到無效的憑據,則該管道將繼續運行,即使還沒有驗證未確定的身份。只有根據後來在管道中啟動並執行組件,才能確定如何處理這個匿名請求。
在最基本的層面上,授權篩選器只檢查所確定的身份是否是經過身分識別驗證的身份。然而,授權篩選器也可以確保:
- 經過身分識別驗證的身份的使用者名稱在經過允許的使用者列表上。
- 至少有一個與經過身分識別驗證的身份相關的角色會列在經過允許的角色列表上。
雖然開箱即用的授權篩選器只根據剛才的描述執行角色型存取控制,但是來自開箱即用授權篩選器的自訂授權篩選器卻可以通過檢查屬於由身分識別驗證篩選器確定的身份的聲明來執行基於聲明的存取控制。
如果所有的授權篩選器都運行正常,管道將繼續執行,最終 API 控制器的操作方法會產生一個針對請求的響應。如果未確定身份,或者如果在使用者名稱或角色要求方面存在不匹配,則授權篩選器將拒絕存在 401 未授權響應的請求。圖 1 說明了兩種篩選器在三種情況下所扮演的角色:不存在憑據、提供的憑據無效和存在的憑據有效。
圖 1 ASP.NET Web API 管道中的安全性篩選器
建立身分識別驗證篩選器
身分識別驗證篩選器是一個實現 IAuthenticationFilter 介面的類。這個介面提供兩種方法:AuthenticateAsync 和 ChallengeAsync,如下所示:
public interface IAuthenticationFilter : IFilter{ Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken); Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken);}
AuthenticateAsync 方法接受 HttpAuthenticationContext 作為參數。此上下文就是 AuthenticateAsync 方法將身分識別驗證的結果反饋給 ASP.NET Web API 架構的方式。如果請求訊息中包含真實的憑據,傳入 HttpAuthenticationContext 對象的 Principal 屬性將被設定為經過身分識別驗證的主體。如果憑據無效,HttpAuthenticationContext 參數的 ErrorResult 屬性將設定為 UnauthorizedResult。如果該請求訊息根本不包含憑據,則 AuthenticateAsync 方法不執行任何操作。圖 2 中的代碼顯示了涵蓋這三種情況的 AuthenticateAsync 方法的典型實現。在這個樣本中使用的經過身分識別驗證的主體是只有名稱和角色聲明的 ClaimsPrincipal。
圖 2 AuthenticateAsync 方法
public Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken){ var req = context.Request; // Get credential from the Authorization header //(if present) and authenticate if (req.Headers.Authorization != null && "somescheme".Equals(req.Headers.Authorization.Scheme, StringComparison.OrdinalIgnoreCase)) { var creds = req.Headers.Authorization.Parameter; if(creds == "opensesame") // Replace with a real check { var claims = new List<Claim>() { new Claim(ClaimTypes.Name, "badri"), new Claim(ClaimTypes.Role, "admin") }; var id = new ClaimsIdentity(claims, "Token"); var principal = new ClaimsPrincipal(new[] { id }); // The request message contains valid credential context.Principal = principal; } else { // The request message contains invalid credential context.ErrorResult = new UnauthorizedResult( new AuthenticationHeaderValue[0], context.Request); } } return Task.FromResult(0);}
您可以使用 AuthenticateAsync 方法來實現驗證請求中的憑據的核心身分識別驗證邏輯,並使用 ChallengeAsync 方法添加驗證挑戰。當狀態碼是 401 未經授權時,驗證挑戰被加入到響應中,為了檢查狀態碼,您需要該響應對象。但 ChallengeAsync 方法不允許您檢查響應或直接設定質詢。事實上,這種方法是在操作方法之前在 Web API 管道的請求處理部分中執行的。然而,ChallengeAsync 方法的參數 HttpAuthenticationChallengeContext 允許將操作結果對象 (IHttpActionResult) 分配給 Result 屬性。操作結果對象的 ExecuteAsync 方法等待任務產生響應,檢查響應狀態碼,並添加 WWW-Authenticate 響應標題。圖 3 中的代碼顯示了 ChallengeAsync 方法的典型實現。在這個樣本中,我只添加一個經過硬式編碼質詢。ResultWithChallenge 類是我建立用來添加質詢的操作結果類。
圖 3 ChallengeAsync 方法
public Task ChallengeAsync(HttpAuthenticationChallengeContext context, CancellationToken cancellationToken){ context.Result = new ResultWithChallenge(context.Result); return Task.FromResult(0);}public class ResultWithChallenge : IHttpActionResult{ private readonly IHttpActionResult next; public ResultWithChallenge(IHttpActionResult next) { this.next = next; } public async Task<HttpResponseMessage> ExecuteAsync( CancellationToken cancellationToken) { var response = await next.ExecuteAsync(cancellationToken); if (response.StatusCode == HttpStatusCode.Unauthorized) { response.Headers.WwwAuthenticate.Add( new AuthenticationHeaderValue("somescheme", "somechallenge")); } return response; }}
下面的代碼顯示了完整的篩選器類:
public class TokenAuthenticationAttribute : Attribute, IAuthenticationFilter{ public bool AllowMultiple { get { return false; } } // The AuthenticateAsync and ChallengeAsync methods go here}
除了實現 IAuthenticationFilter 介面,從屬性中派生可以將此類用作類(控制器)級或方法(操作方法)級的屬性。
因此,您可以建立一個只負責身分識別驗證特定憑據(本例中的虛假令牌)的身分識別驗證篩選器。身分識別驗證篩選器沒有授權邏輯;它唯一目的是處理身分識別驗證:(在處理請求訊息時如果有的話)確定身份,(在處理響應訊息時如果有的話)返回質詢。授權篩選器處理授權問題,如檢查身份是否是經過身分識別驗證的身份或者已在經過允許的使用者或角色列表中列出。
使用授權篩選器
使用授權篩選器的基本目標是執行授權,以確定使用者是否有權訪問所請求的資源。Web API 提供了所謂的 AuthorizeAttribute 授權篩選器的使用。應用該篩選器可確保身份是經過身分識別驗證的身份。您還可以使用允許的特定使用者名稱和角色列表配置授權屬性。圖 4 中的代碼顯示了使用不同的身份屬性來授權,在不同層級(總的來說,是控制器層級和操作方法層級)應用的授權篩選器。本樣本中的篩選器整體上保證了身份是經過身分識別驗證了的。在控制器層級使用的篩選器保證了身份是經過身分識別驗證了的,並且與該身份相關聯的角色中至少有一個是“管理員”。在操作方法層級使用的篩選器確保了身份是經過身分識別驗證的,且使用者名稱是“badri”。這裡要注意的一點是,在操作方法層級的授權篩選器也繼承了控制器層級和全域層級的篩選器。因此,若要成功完成授權,所有篩選器都必須通過:使用者名稱必須是“badri”,其中一個角色必須是“管理員”,且使用者必須經過身分識別驗證。
圖 4 使用處於三個不同層級的授權篩選器
public static class WebApiConfig{ public static void Register(HttpConfiguration config) { // Other Web API configuration code goes here config.Filters.Add(new AuthorizeAttribute()); // Global level }}[Authorize(Roles="admin")] // Controller levelpublic class EmployeesController : ApiController{ [Authorize(Users="badri")] // Action method level public string Get(int id) { return “Hello World”; }}
開箱即用的 AuthorizeAttribute 非常有用,但是如果需要更多的自訂,您可以對其劃分子類,來實現其他的授權行為。下面的代碼顯示了一個自訂的授權篩選器:
public class RequireAdminClaimAttribute : AuthorizeAttribute{ protected override bool IsAuthorized(HttpActionContext context) { var principal = context.Request.GetRequestContext().Principal as ClaimsPrincipal; return principal.Claims.Any(c => c.Type == "http://yourschema/identity/claims/admin" && c.Value == "true"); }}
這個篩選器只檢查“管理員”自訂聲明,但您可以使用 HttpActionContext 中的主體和其他附加資訊在這裡進行自訂授權。
在 ASP.NET Web API 的第一個版本中,自訂授權篩選器經常被誤用來實現身分識別驗證,但對於 ASP.NET Web API 2,身分識別驗證篩選器現在管道中有自己的地方,這有助於開發乾淨的模組化代碼,便於分開考慮身分識別驗證和授權方面的問題。
篩選器覆蓋
正如我剛才解釋的,授權篩選器可以應用在操作方法層級、控制器層級或全域層級。通過全域指定授權篩選器,您可以在所有控制器範圍內強制執行對所有操作方法調用的授權。如果您想通過一些方法免於執行全域配置檢查,那麼使用 AllowAnonymous 屬性可以輕鬆做到這一點。
圖 5 中的代碼顯示了在控制器層級對 AllowAnonymous 屬性的使用。雖然授權篩選器在全域範圍中應用,但與 PublicResourcesController 一起使用的 AllowAnonymous 屬性可免於對傳入此控制器的請求執行授權。
圖 5 使用 AllowAnonymous 屬性
public static class WebApiConfig{ public static void Register(HttpConfiguration config) { // Other Web API configuration code goes here config.Filters.Add(new AuthorizeAttribute()); // Global level }}[AllowAnonymous]public class PublicResourcesController : ApiController{ public string Get(int id) { return “Hello World”; }}
AllowAnonymous 屬性提供了一種方式,讓特定的操作可以覆蓋由更進階別的授權篩選器配置的授權。然而,AllowAnonymous 只允許您覆蓋授權。假設您最想要使用 HTTP 基本驗證對大多數操作進行身分識別驗證,但有一個操作只能用令牌進行身分識別驗證。全域配置令牌身分識別驗證而隨後對此操作覆蓋身分識別驗證(類似於 AllowAnonymous 覆蓋授權的方式)會是不錯的方法。
ASP.NET Web API 2 引入了一種新的篩選器類型,以解決這種情況,即覆蓋篩選器。不同於 AllowAnonymous,ASP.NET Web API 2 中引入的覆蓋篩選器可與任何類型的篩選器一同工作。覆蓋篩選器,顧名思義,可以覆蓋在更進階別上配置的篩選器。若要覆蓋在更高層次上配置的身分識別驗證篩選器,請使用開箱即用的屬性 OverrideAuthentication。如果您有一個適合全域使用的身分識別驗證篩選器,並希望阻止它運行特定的操作方法或控制器,您可以僅在所需的層級上應用 OverrideAuthentication。
覆蓋篩選器的作用遠不止於阻止某些篩選器運行。假設您有兩種身分識別驗證篩選器,一個用於驗證安全性權杖,另一個用於在 HTTP 基本方案中驗證使用者名稱/密碼。這兩種篩選器都在全域範圍內應用,使您的 API 足夠靈活,可以接受令牌或使用者名稱/密碼。下面的代碼顯示了在全域範圍內應用的兩種身分識別驗證篩選器:
public static class WebApiConfig{ public static void Register(HttpConfiguration config) { // Other Web API configuration code goes here config.Filters.Add(new TokenAuthenticator()); config.Filters.Add(new HttpBasicAuthenticator(realm: "Magical")); }}
現在,也許您想確保只將令牌用作訪問特定操作方法的憑據。OverrideAuthentication 如何使您能夠滿足這種需求,難道是它禁止所有篩選器運行?下面是覆蓋篩選器的重要特徵,他們清除了在更進階別上指定的所有篩選器,但不刪除與其在同一層級上指定的篩選器。這基本上意味著您可以在某個特定層級上添加一個或多個身分識別驗證篩選器,同時清除在更進階別上的所有其他篩選器。回到僅將令牌用作為訪問特定操作方法的憑據的要求,您可以簡單地在操作方法層級上指定 OverrideAuthentication 屬性和 TokenAuthenticator 屬性,如下面的代碼所示(這可以確保只有 TokenAuthenticator 為操作方法 GetAllowedForTokenOnly 運行):
public class EmployeesController : ApiController{ [OverrideAuthentication] // Removes all authentication filters [TokenAuthenticator] // Puts back only the token authenticator public string GetAllowedForTokenOnly(int id) { return “Hello World”; }}
因此,引入了 ASP.NET Web API 2 的覆蓋篩選器在全域範圍內指定篩選器,並在較低層級上選擇性地在必須只對全域行為進行覆蓋的領域運行篩選器方面提供了更大的靈活性。
除了 OverrideAuthentication 屬性,另外還有開箱即用的稱為 OverrideAuthorization 的屬性,它可以刪除在更進階別上指定的授權篩選器。與 AllowAnonymous 相比,不同之處在於 OverrideAuthorization 只刪除更進階別上的授權篩選器。但不刪除與其在相同層級上的指定的授權篩選器。AllowAnonymous 使 ASP.NET Web API 可以跳過相關的授權過程,即使是與 AllowAnonymous 在相同層級上指定的授權篩選器,都將被忽略。
總結
您可以使用由主機提供的選項,以及由 ASP.NET Web API 管道提供的擴充點在 ASP.NET Web API 中執行身分識別驗證。基於主機的選項很好地整合到主機管道中,並在早期拒絕管道中的無效請求。ASP.NET Web API 擴充點提供對身分識別驗證過程更精細的控制層級。如果您需要對身分識別驗證執行更多的控制,例如,要對不同的控制器,甚至不同的操作方法使用不同的身分識別驗證機制,正確的做法是,通過身分識別驗證篩選器解決身分識別驗證相關問題以及通過授權篩選器解決授權相關問題。
ASP.NET Web API 安全性篩選器