ASP.NET Web API 安全篩選器

原文: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 的擴展選項對身份驗證過程提供更精細的控制水平。也就是說,您能夠對不一樣的控制器甚至不一樣的操做方法設置不一樣的身份驗證機制。權衡與主機更好地集成在一塊兒,並較早對不佳的身份驗證粒度請求予以拒絕。除了這些常規特性,每一個選項都有本身的優缺點,我將在後面的章節中進行介紹。async

HTTP 模塊這是在 IIS 上運行的一個 Web API 選項。做爲 IIS 管道的一部分,HTTP 模塊容許較早地執行安全代碼。從 HTTP 模塊中創建的主體適用於全部的組件,包括管道中稍後運行的 IIS 組件。例如,若是主體是由響應 AuthenticateRequest 事件的 HTTP 模塊構建的,則主體的用戶名將被正確地記錄在 IIS 日誌的 cs-username 字段中。HTTP 模塊的最大缺點是缺少粒度。HTTP 模塊對進入應用程序的全部請求作出運行反應。對於具備不一樣功能(如 HTML 標記生成,Web API 等等)的 Web 應用程序,讓一個 HTTP 模塊以某種方式強制執行身份驗證一般不是一個很靈活的方法。在這種狀況下,另外一個使用 HTTP 模塊的缺點就顯現出來了,即依賴主機—IIS。ide

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 請求運行。使用消息處理程序的不足之處在於缺少更精細的控制。可將消息處理程序配置爲對全部請求或對特定路由以全局處理程序來運行。對於給定的路由,您能夠有多個控制器。全部這些控制器和它們所包含的操做方法都必須共享相同的由爲此路由配置的消息處理程序強制執行的身份驗證。換句話說,由消息處理程序執行的身份驗證的最低粒度是在路由級別。ui

操做篩選器由 ASP.NET Web API 提供的另外一個擴展選項是操做篩選器。然而,從執行身份驗證的角度來看,它不是一個可行的選擇,僅僅是由於它在受權篩選器在 ASP.NET Web API 管道運行以後纔開始運行。爲了讓身份驗證和受權能正常工做,身份驗證必須先於受權而運行。this

受權篩選器然而,由 ASP.NET Web API 提供的另外一個擴展選項是受權篩選器。對於要求比消息處理程序能提供的更高的粒度的情形,執行自定義身份驗證最多見的一種方式是使用受權篩選器。將受權篩選器用於身份驗證和受權的主要問題是,ASP.NET Web API 並不保證身份驗證篩選器的執行順序。基本上,這意味着在執行身份驗證的受權篩選器運行以前,執行受權的受權篩選器就能夠正常運行了,從而使得受權篩選器選項如同操做篩選器選項同樣不適合身份驗證。編碼

身份驗證篩選器這是本文的重點所在,它是可用於 ASP.NET Web API 2 的最新擴展選項。身份驗證篩選器在消息處理程序以後運行,而且是在其餘全部篩選器類型以前運行。所以,它們是實現身份驗證相關操做的更好選擇。最重要的是,身份驗證篩選器是在受權篩選器以前運行的。經過使用專門針對身份驗證或受權的篩選器,能夠分別處理身份驗證和受權相關的問題。

此外,身份驗證篩選器提供控制或粒度級別,所以特別有用。以旨在被本機移動應用程序和基於瀏覽器的 AJAX 應用程序所使用的 Web API 爲例。移動應用程序可能會在 HTTP Authorization 標頭中顯示一個令牌,而 AJAX 應用程序可能將身份驗證 Cookie 用做憑據。此外,假設 API 的子集是敏感的,且僅適用於本機移動應用程序,您要確保只能經過提供令牌,而不是提供 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 未經受權狀態代碼的請求。它根本沒有肯定一個通過身份驗證的身份,並將如何處理匿名請求的問題留給了受權階段。身份驗證篩選器基本執行三種類型的操做:

  1. 若是感興趣的憑據不存在於該請求中,則篩選器就不執行任何操做。
  2. 若是存在憑據且該憑據是有效的,則篩選器會以通過身份驗證的主體的形式肯定一個身份。
  3. 若是存在憑據但該憑據是無效的,篩選器就會經過設置一個錯誤的結果通知 ASP.NET Web API 框架,這基本上可致使向請求者發回一個「未經受權」的響應。

若是管道中運行的身份驗證篩選器都沒法檢測到無效的憑據,則該管道將繼續運行,即便尚未驗證未肯定的身份。只有根據後來在管道中運行的組件,才能肯定如何處理這個匿名請求。

在最基本的層面上,受權篩選器只檢查所肯定的身份是不是通過身份驗證的身份。然而,受權篩選器也能夠確保:

  • 通過身份驗證的身份的用戶名在通過容許的用戶列表上。
  • 至少有一個與通過身份驗證的身份相關的角色會列在通過容許的角色列表上。

雖然開箱即用的受權篩選器只根據剛纔的描述執行基於角色的訪問控制,可是來自開箱即用受權篩選器的自定義受權篩選器卻能夠經過檢查屬於由身份驗證篩選器肯定的身份的聲明來執行基於聲明的訪問控制。

若是全部的受權篩選器都運行正常,管道將繼續執行,最終 API 控制器的操做方法會生成一個針對請求的響應。若是未肯定身份,或者若是在用戶名或角色要求方面存在不匹配,則受權篩選器將拒絕存在 401 未受權響應的請求。圖 1 說明了兩種篩選器在三種狀況下所扮演的角色:不存在憑據、提供的憑據無效和存在的憑據有效。

ASP.NET Web API 管道中的安全篩選器 圖 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 level
public 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 擴展點提供對身份驗證過程更精細的控制級別。若是您須要對身份驗證執行更多的控制,例如,要對不一樣的控制器,甚至不一樣的操做方法使用不一樣的身份驗證機制,正確的作法是,經過身份驗證篩選器解決身份驗證相關問題以及經過受權篩選器解決受權相關問題。

相關文章
相關標籤/搜索