本文章是由機器翻譯。

ASP.NET

啟用及自訂 ASP.NET Web API 服務安全性

Peter Vogel

為最常見的情形 — — 訪問同一網站上的 Web API 服務在 Web 頁中的 JavaScript — — 討論安全性為ASP.NETWeb API 是幾乎冗余。 規定,您對您的使用者進行身份驗證和授權訪問到 Web 表單/意見持有的 JavaScript,消耗你的服務,你可能提供所有的安全您的服務需要。 這是ASP.NET發送 cookie 和身份驗證資訊,它使用作為一部分的任何對您的服務方法的用戶端 JavaScript 請求驗證頁面請求的結果。 有一個例外 (和它是一個十分重要):ASP.NET不會自動保護你的攻擊跨網站請求偽造 (CSRF/XSRF) (還有更多的以後)。

除了 CSRF,它不會自我感覺要討論保護您的 Web API 服務時可能會有兩種方案。 第一種情形是當您的服務由用戶端在相同的網站作為您的 ApiControllers 頁面之外的消耗。 這些用戶端不會已經過身份驗證通過表單身份驗證和不會獲得了 cookie 和權杖ASP.NET用來控制對您的服務的訪問。

第二種情況發生時您希望添加額外授權您超越什麼通過ASP.NET安全提供的服務。 ASP.NET提供的預設授權基於ASP.NET將分配給請求身份驗證期間的身份。 您可能希望將擴展該標識來授予存取權限的標識名稱或角色的基礎的東西。

Web API 為您提供了多個選項來解決這兩種方案。 事實上,雖然因為 Web API 基於相同的ASP.NETWeb 表單和 MVC 基礎,我將討論在接受 Web API 請求的上下文中的安全,我會在這篇文章仲介紹的工具要被熟悉的人已經在引擎蓋下與 Web 表單或 MVC 中的安全性。

這裡要提出一個警告:雖然 Web API 提供了幾種身份驗證和授權,安全的選擇與您開始與主機,IIS 或您創建時自已的主機承載。 如果,例如,你想要確保隱私的 Web API 服務和用戶端之間的通信,然後你應該,至少,在打開 SSL。 這,然而,是網站管理員,而不是開發人員的責任。 在這篇文章我要忽略的主機,專注于開發人員的可以 — — 並且應該 — — 做安全的 Web API 服務 (和我會在這裡討論的工具工作如果 SSL 打開或關閉)。

防止跨網站請求偽造攻擊

當使用者訪問使用表單身份驗證ASP.NET網站時,ASP.NET生成一個 cookie,規定對使用者進行驗證。 瀏覽器將繼續發送,每個後續請求的 cookie 到網站,無論出自這一請求。 這 CSRF 攻擊,您的網站一樣打開任何身份驗證方案,瀏覽器會自動將發送以前收到的身份驗證資訊。 如果您的網站提供安全 cookie 與瀏覽器後,使用者訪問一些惡意網站,然後該網站可以向您的服務,坐享其成,身份驗證 cookie 在瀏覽器收到了早些時候發送請求。

若要防止 CSRF 攻擊,你需要生成伺服器上的 antiforgery 標記並將其嵌入在網頁中用於在您的用戶端調用中使用。 Microsoft 提供的 AntiForgery 類與 GetToken 的方法,將生成標記特定于使用者提出這一要求 (,當然,可能匿名使用者)。 此代碼將生成兩個標記,並把它們放在ASP.NETMVC ViewBag,他們可以在視圖中使用的地方:

[Authorize(Roles="manager")]
public ActionResult Index()
{
  string cookieToken;
  string formToken;
  AntiForgery.GetTokens(null, out cookieToken, out formToken);
  ViewBag.cookieToken = cookieToken;
  ViewBag.formToken = formToken;
  return View("Index");
}

對該伺服器的任何 JavaScript 調用將需要返回標記作為請求的一部分 (CSRF 網站不會有這些權杖和不能將它們返回)。 此代碼中,在視圖中,動態地生成一個 JavaScript 調用,將標記添加到請求標頭:

$.ajax("http://phvis.com/api/Customers",{
  type: "get",
  contentType: "application/json",
  headers: {
    'formToken': '@ViewBag.formToken',
    'cookieToken': '@ViewBag.cookieToken' }});

一個稍微複雜的解決方案會讓你通過在視圖中的隱藏欄位中嵌入標記使用不顯眼的 JavaScript。 在這一過程的第一步是將標記添加到 ViewData 字典:

ViewData["cookieToken"] = cookieToken;
ViewData["formToken"] = formToken;

現在,在視圖中,您可以嵌入隱藏欄位中的資料。 HtmlHelper 的隱藏方法只需要在 ViewDate 生成正確的輸入的標記傳遞鍵的值:

@Html.Hidden("formToken")

所生成的輸入的標記將標記的名稱和 id 屬性使用 ViewData 鍵並放入標記的 value 屬性從 ViewData 字典檢索的資料。 從前面的代碼中生成的輸入的標記將如下所示:

<input id="formToken" name="formToken" type="hidden" value="...token..." />

JavaScript 代碼 (保存在一個單獨的檔中從視圖) 可以檢索中輸入標籤的值,然後在您的 ajax 調用中使用它們:

$.ajax("http://localhost:49226/api/Customers", {
  type: "get",
  contentType: "application/json",
  headers: {
    'formToken': $("#formToken").val(),
    'cookieToken': $("#cookieToken").val()}});

可以通過使用上 (可從該頁面的 ClientScript 屬性檢索) 的 ClientScriptManager 物件的 RegisterClientScriptBlock 方法插入與嵌入標記的 JavaScript 代碼來實現ASP.NETWeb 表單網站中相同的目標:

string CodeString = "function CallService(){" +
  "$.ajax('http://phvis.com/api/Customers',{" +
  "type: 'get', contentType: 'application/json'," +
  "headers: {'formToken': '" & formToken & "',” +
  "'cookieToken': '" & cookieToken & "'}});}"
this.ClientScript.RegisterClientScriptBlock(
  typeOf(this), "loadCustid", CodeString, true);

最後,您需要驗證的標記在伺服器上,他們要返回時由 JavaScript 調用。 Visual Studio2012年已經應用該ASP.NET和 Web 工具 2012.2 更新的使用者會發現新的單頁面應用程式 (SPA) 範本包括一個 ValidateHttpAntiForgeryToken 篩選器,可用於 Web API 方法。 在沒有該篩選器的情況下,您會需要檢索權杖並將它們傳遞到 AntiForgery 類驗證方法 (Validate 方法將引發異常,如果權杖不有效,或為不同的使用者生成了)。 中的代碼圖 1、 用於 Web API 服務方法中、 從標題檢索權杖和驗證這些。

圖 1 驗證 CSRF 權杖在服務的方法

public HttpResponseMessage Get(){
  if (Request.Headers.TryGetValues("cookieToken", out tokens))
  {
    string cookieToken = tokens.First();
    Request.Headers.TryGetValues("formToken", out tokens);
    string formToken = tokens.First();
    AntiForgery.Validate(cookieToken, formToken);
  }
  else
  {
    HttpResponseMessage hrm =
      new HttpResponseMessage(HttpStatusCode.Unauthorized);
    hrm.ReasonPhrase = "CSRF tokens not found";
    return hrm;
  } 
  // ...
Code to process request ...

使用 ValidateHttpAntiForgeryToken (而不是代碼的方法內) 移動到較早前在週期中的處理 (之前,例如,模型綁定),這是一件好事。

為什麼沒有 OAuth 嗎?

這篇文章刻意忽略 OAuth。 OAuth 技術條件­揚天定義了用戶端從一個協力廠商伺服器來發送給服務將,反過來,驗證與協力廠商伺服器的權杖可以檢索標記的方式。 討論如何訪問 OAuth 權杖提供程式可以從用戶端或服務已超出本文的範圍。

OAuth 的初始版本還不是非常匹配的 Web API。 據推測,使用 Web API 的主要原因之一是使用基於休息和 JSON 的輕量請求。 這一目標使 OAuth 的第一個版本不具吸引力的 Web API 服務選項。 笨重和基於 XML 的 OAuth 的第一個版本由指定的標記。 幸運的是,OAuth 2.0 引入了一個輕量 JSON 標記,是更緊湊,比從以前的版本戳記的規範。 據推測,在本文中討論的技術可以用於處理任何 OAuth 權杖發送到您的服務。

基本驗證

你有在 Web API 服務的安全中的兩個主要職責的第一是身份驗證 (其他責任被授權)。 我會認為其他的問題 — — 隱私,例如 — — 在主機處理。

理想情況下,身份驗證和授權將執行 Web API 管道以避免花費您打算拒絕的請求的處理週期中盡可能早。 這篇文章的身份驗證解決方案可用於很早在管道中 — — 實際上,一旦收到的要求。 這些技術還允許您與您已經要維護無論使用者清單集成身份驗證。 討論的授權技術可應用於多種管道 (包括作為晚在本身的服務方法) 中的位置和身份驗證,授權請求基於一些其他條件比使用者的名稱或角色可以工作。

您可以支援還沒走過表單身份驗證提供您自己的身份驗證方法 (我仍然假設在這裡您不正在驗證 Windows 帳戶而是針對您自己的有效使用者的清單) 的自訂 HTTP 模組中的用戶端。 有使用 HTTP 模組的兩個主要好處:一個模組參加 HTTP 日誌記錄和審核 ; 此外,模組調用很早在管道中。 雖然這些都是好事,但模組跟兩個費用:模組是全球性的適用于所有請求到網站,不只是 Web API 請求 ; 此外,若要使用的身份驗證模組,必須承載您在 IIS 中的服務。 後來在本文中,我將討論使用委派處理常式只對 Web API 請求調用,它們是主機-不可知論者。

對於此示例中使用的 HTTP 模組,我假定 IIS 使用基本驗證和安全信任到所使用的憑據­ticate 使用者的使用者名和密碼,用戶端發送 (在本文中,就會忽略 Windows 認證,但將討論使用用戶端憑證)。 我也假設我保護的 Web API 服務安全使用授權屬性,指定的使用者:

public class CustomersController : ApiController
{
  [Authorize(Users="Peter")]
  public Customer Get()
  {

創建自訂的授權 HTTP 模組的第一步是將類添加到您的服務專案,實現了 IHttpModule 和 idisposable 可以根據介面。 在類的初始化方法你需要電線從 HttpApplication 物件的兩個事件傳遞給該方法。 用戶端的憑據提交時,將調用您將附加到 AuthenticateRequest 事件的方法。 但你也必須為生成的消息,使用戶端向您發送它的憑據而再線的 EndRequest 方法。 你還需要 Dispose 方法,但你不需要放任何東西在它支援在這裡使用的代碼:

public class PHVHttpAuthentication : IHttpModule, IDisposable
{
  public void Init(HttpApplication context)
  {
    context.AuthenticateRequest += AuthenticateRequests;
    context.EndRequest += TriggerCredentials;
  }
  public void Dispose()
  {
  }

HttpClient 將憑據發送回應 WWW-­您在 HTTP 回應中包含的身份驗證標頭。 當請求生成 401 狀態碼 (ASP.NET時將生成一個 401 回應代碼,用戶端將被拒絕訪問一個擔保服務) 時,應包括該標頭。 標頭必須提供提示正在使用的身份驗證方法和在其中,身份驗證將適用的領土 (領土可以是任何任意的字串和用於標記到瀏覽器的不同區域的伺服器上)。 要發送該消息的代碼是你放有線到 EndRequest 事件的方法。 此示例生成一條消息,指定 PHVIS 領土內正在使用基本驗證:

private static void TriggerCredentials(object sender, EventArgs e)
{
  HttpResponse resp = HttpContext.Current.Response;
  if (resp.StatusCode == 401)
  {
    resp.Headers.Add("WWW-Authenticate", @"Basic realm='PHVIS'");
  }
}

在方法內你過有線到的 AuthenticateRequest 方法,您需要檢索用戶端將發送由於接收您 401/WWW 身份驗證的消息的授權標頭:

private static void AuthenticateRequests(object sender,
  EventArgs e)
{
  string authHeader =     
    HttpContext.Current.
Request.Headers["Authorization"];
  if (authHeader != null)
  {

一旦您確定用戶端已通過授權標頭元素 (和繼續與我較早的假設,該網站將使用基本驗證),您需要分析出持有的使用者名和密碼資料。 使用者名和密碼是 Base64 編碼和由冒號分隔。 此代碼檢索到兩個位置的字串陣列中的使用者名和密碼:

AuthenticationHeaderValue authHeaderVal =
  AuthenticationHeaderValue.Parse(authHeader);
if (authHeaderVal.Parameter != null)
{
  byte[] unencoded = Convert.FromBase64String(
    authHeaderVal.Parameter);
  string userpw =
    Encoding.GetEncoding("iso-8859-1").GetString(unencoded);
  string[] creds = userpw.Split(':');

此代碼表明,以明文發送的使用者名和密碼。 如果你不開啟 SSL 然後輕鬆地捕獲您的使用者名和密碼 (和此代碼的工作即使 SSL 處於打開狀態)。

下一步是驗證的使用者名和密碼使用不管什麼機制對你有意義。 不管如何你驗證的請求 (在下面的示例中使用的代碼可能是過於簡單的),你的最後一步是要在稍後ASP.NET管道中的授權進程中創建的使用者,將使用的身份。

那身份通過傳遞資訊管道,您與您想要將分配給使用者 (在下面的代碼我已經假定身份是標頭中發送的使用者名) 標識的名稱創建一個 GenericIdentity 物件。 一旦您已經創建的 GenericIdentity 物件,你必須放在該執行緒類的 CurrentPrincipal 屬性。 ASP.NET還保持的 HttpCoNtext 物件中的第二個安全上下文和,如果您的主機是 IIS,您必須支援,還在 HttpCoNtext 的當前屬性設置為您的 GenericIdentity 物件中設置使用者屬性:

if (creds[0] == "Peter" && creds[1] == "pw")
{
  GenericIdentity gi = new GenericIdentity(creds[0]);
  Thread.CurrentPrincipal = new GenericPrincipal(gi, null);
  HttpContext.Current.User = Thread.CurrentPrincipal;
}

如果您想要支援基於角色的安全性,然後您必須傳遞一個陣列的角色名稱作為第二個參數到 GenericPrincipal 建構函式。 本示例將每個使用者指派給經理和管理員角色:

string[] roles = "manager,admin".Split(',');
Thread.CurrentPrincipal = new GenericPrincipal(gi, roles);

若要將您的 HTTP 模組集成到您的網站的處理,在您的專案的 web.config 檔中,請使用模組元素內的委任標記。 委任標記的 type 屬性必須設置為您的模組的程式集名稱後面跟的完全限定的類名稱由組成的字串:

<modules>
  <add name="myCustomerAuth"
    type="SecureWebAPI.PHVHttpAuthentication, SecureWebAPI"/>
</modules>

您創建的 GenericIdentity 物件將與ASP.NET授權特性工作。 您也可以訪問 GenericIdentity 從裡面服務的一種方法來執行授權活動。 例如,可以通過確定如果使用者已通過身份驗證,通過檢查 GenericIdentity 物件 (IsAuthenticated 返回 false 為匿名使用者) 的 IsAuthenticated 屬性來提供不同服務的使用者已登錄和匿名使用者:

if (Thread.CurrentPrincipal.Identity.IsAuthenticated)
{

您可以檢索更加簡單地通過使用者屬性的 GenericIdentity 物件:

if (User.Identity.IsAuthenticated)
{

建設相容的用戶端

以消費服務受此模組中,非-­JavaScript 用戶端必須提供可以接受使用者名和密碼。 要提供這些憑據使用.NET HttpClient,您首先創建一個 HttpClientHandler 物件和將其憑據屬性設置為一個 NetworkCredential 物件,該物件保存的使用者名和密碼 (或 HttpClientHandler 物件使用屬性設為 true,以使用當前使用者的 Windows 憑據)。 您然後創建您的 HttpClient 物件,通過 HttpClientHandler 物件:

HttpClientHandler hch = new HttpClientHandler();
hch.Credentials = new NetworkCredential ("Peter", "pw");
HttpClient hc = new HttpClient(hch);

與該做的配置,您可以發佈您的請求到該服務。 HttpClient 不會出示憑據,直到它已被拒絕訪問該服務,並已收到了 WWW 身份驗證的消息。 如果由 HttpClient 提供的憑據不是可以接受的該服務返回 HttpResponseMessage 與 StatusCode 的它的結果設置為"未驗證"。

下面的代碼調用服務使用 GetAsync 方法,取得成功的結果檢查並 (如果它不能得到一個) 顯示的狀態碼從服務返回:

hc.GetAsync("http://phvis.com/api/Customers").ContinueWith(r =>
{
  HttpResponseMessage hrm = r.Result;
  if (hrm.IsSuccessStatusCode)
  {
    // ...
Process response ...
}
  else
  {
    MessageBox.Show(hrm.StatusCode.ToString());
  }
});

假設您繞過ASP.NET登錄過程的非-­用戶端 JavaScript 一樣在這裡,沒有身份驗證 cookie 將會創建和將單獨驗證從用戶端的每個請求。 要減少上反復驗證憑據提供由用戶端,您應該考慮緩存憑據的開銷你檢索服務 (和使用您的 Dispose 方法要丟棄這些緩存的憑據)。

使用用戶端憑證

在 HTTP 模組中,您檢索用戶端憑證物件 (和確保它是目前和有效) 用以下代碼:

System.Web.HttpClientCertificate cert =
  HttpContext.Current.Request.ClientCertificate;
if (cert!= null && cert.IsPresent && cert.IsValid)
{

進一步沿處理管線中 — — 在一個服務方法,例如 — — 您檢索的證書物件 (和檢查,存在一個) 與此代碼:

X509Certificate2 cert = Request.GetClientCertificate();
if (cert!= null)
{

如果證書是有效和現在,你可以另外檢查證書的屬性 (例如,主題或頒發者) 中的特定值。

為了發送 HttpClient 的證書,您的第一步是創建一個 WebRequestHandler 物件,而不是 HttpClientHandler (WebRequestHandler 提供比 HttpClientHandler 更多配置選項):

WebRequestHandler wrh = new WebRequestHandler();

可以通過設置 WebRequestHandler 物件的 ClientCertificateOptions 配置為自動值,從 ClientCertificateOption 枚舉自動搜索用戶端的憑證存放區區的 HttpClient:

wrh.ClientCertificateOptions = ClientCertificateOption.Manual;

預設情況下,但是,用戶端必須顯式證書從將附加到 WebRequestHandler 的代碼。 像本示例一樣,從 CurrentUser 的存儲區使用頒發者的名稱檢索證書,您可以從一個用戶端的憑證存放區區中檢索證書:

X509Store certStore;
X509Certificate x509cert;
certStore = new X509Store(StoreName.My, 
  StoreLocation.CurrentUser);  
certStore.Open(OpenFlags.OpenExistingOnly | OpenFlags.ReadOnly);
x509cert = certStore.Certificates.Find(
  X509FindType.FindByIssuerName, "PHVIS", true)[0];
store.Close();

如果使用者已發送的用戶端憑證,出於某些原因,不會被添加到使用者的憑證存放區區,然後你可以創建 x509 證書的物件從的證書檔,像這樣的代碼:

x509cert = new X509Certificate2(@"C:\PHVIS.pfx");

X509 證書的創建方式,無論在用戶端的最後步驟是將證書添加到 WebRequestHandler ClientCertificates 集合中,然後使用已配置的 Web­RequestHandler 來創建 HttpClient:

wrh.ClientCertificates.Add(x509cert);
hc = new HttpClient(wrh);

在託管環境中授權

雖然您不能在託管環境中使用 HTTP 模組所,確保請求處理管道的自託管服務在早期的過程是相同的:從請求中獲取憑據,使用這些資訊來對請求進行身份驗證並創建一個標識要傳遞到當前執行緒的 CurrentPrincipal 屬性。 最簡單的機制是創建一個使用者名和密碼的驗證器。 要做更多隻是驗證使用者名和密碼組合,您可以創建一個委託處理常式。 首先會看整合一個使用者名和密碼的驗證器。

若要創建一個驗證器 (仍然假設你正在使用基本驗證),必須創建一個類,使用者將繼承­NamePasswordValidator (您會需要對 System.IdentityModel 庫的引用添加到專案)。 從您需要重寫的基類的唯一方法是驗證方法,將會通過的使用者名和密碼由用戶端發送到服務。 如之前,一旦你已經通過驗證的使用者名和密碼,你必須創建一個 GenericPrincipal 物件並使用它來將 CurrentPrincipal 屬性設置為該執行緒類 (因為不使用 IIS 作為您的主機,你不設置 HttpCoNtext 使用者屬性):

public class PHVValidator :
  System.IdentityModel.Selectors.UserNamePasswordValidator
{
  public override void Validate(string userName, string password)
  {
    if (userName == "Peter" && password == "pw")
    {
      GenericIdentity gi = new GenericIdentity(username, null);
      Thread.CurrentPrincipal = gi;
    }

下面的代碼創建的控制器與終結點的 HTTP://phvis.com/MyServices,叫客戶主機,並指定一個新的驗證器:

partial class PHVService : ServiceBase
{
  private HttpSelfHostServer shs;
  protected override void OnStart(string[] args)
  {
    HttpSelfHostConfiguration hcfg =
      new HttpSelfHostConfiguration("http://phvis.com/MyServices");
    hcfg.Routes.MapHttpRoute("CustomerServiceRoute",
      "Customers", new { controller = "Customers" });
    hcfg.UserNamePasswordValidator = new PHVValidator;       
    shs = new HttpSelfHostServer(hcfg);
    shs.OpenAsync();

訊息處理常式

多個驗證的使用者名和密碼,您可以創建一個自訂 Web API 訊息處理常式。 訊息處理常式具有以下幾個好處相比,HTTP 模組:訊息處理常式不被綁到 IIS,所以安全在訊息處理常式中的應用將會與任何主機 ; 訊息處理常式只能使用由 Web API,所以他們為您使用一個進程從與您的 Web 網站的頁面 ; 使用不同的服務提供執行授權 (和分配標識) 的簡單方法 你還可以分配訊息處理常式的特定路由到您的安全代碼調用,只在需要的地方。

創建訊息處理常式的第一步是編寫一個類,從 DelegatingHandler 繼承和重寫其 SendAysnc 方法:

public class PHVAuthorizingMessageHandler: DelegatingHandler
{
  protected override System.Threading.Tasks.Task<HttpResponseMessage>
    SendAsync(HttpRequestMessage request,
      System.Threading.CancellationToken cancellationToken)
  {

在該方法 (和假設您正在創建的每個路由處理常式) 的範圍內您可以設置 DelegatingHandler 的 InnerHandler 屬性,這樣,此處理程式可以連結到管道與其他處理常式:

HttpConfiguration hcon = request.GetConfiguration();
InnerHandler = new HttpControllerDispatcher(hcon);

對於此示例,我要去承擔有效的請求必須在其查詢字串中有一個簡單的權杖 (相當簡單:一個名稱/值對的"authToken = xyx")。 如果權杖中缺少或未設置為 xyx,該代碼返回 403 (禁止) 狀態碼。

我第一次變成查詢字串名稱/值對的一組通過對 HttpRequestMessage 物件傳遞給方法調用 GetQueryNameValuePairs 方法。 我然後使用LINQ檢索權杖 (或如果權杖丟失,則為 null)。 如果權杖丟失或無效,創建 HttpResponseMessage 與適當的 HTTP 狀態碼,把它包在一個 TaskCompletionSource 物件並返回它:

string usingRegion = (from kvp in request.GetQueryNameValuePairs()
                      where kvp.Key == "authToken"
                      select kvp.Value).FirstOrDefault();
if (usingRegion == null || usingRegion != "xyx")
{
  HttpResponseMessage resp =
     new HttpResponseMessage(HttpStatusCode.Forbidden);
  TaskCompletionSource tsc =
     new TaskCompletionSource<HttpResponseMessage>();
  tsc.SetResult(resp);
  return tsc.Task;
}

如果權杖目前和設置為正確的值,創建一個通用的­主要物件並使用它來設置執行緒的 CurrentPrincipal (以支援使用 IIS 下的此訊息處理常式,我也設置的屬性 HttpCoNtext 使用者屬性如果 HttpCoNtext 物件不是 null):

Thread.CurrentPrincipal = new GenericPrincipal(
  Thread.CurrentPrincipal.Identity.Name, null);     
if (HttpContext.Current != null)
{
  HttpContext.Current.User = Thread.CurrentPrincipal;
}

與通過該標記和標識進行身份驗證的請求訊息處理常式設置,調用基方法,以繼續進行處理:

return base.SendAsync(request, cancellationToken);

如果要在每個控制器上使用您的訊息處理常式,您可以將其添加到像任何其他訊息處理常式的 Web API 處理管道。 但是,若要限制到僅在特定的航線上使用您的處理常式,必須通過 MapHttpRoute 方法對它進行添加。 第一,具現化類,然後將它作為第五個參數傳遞給 MapHttpRoute (此代碼需要進口/使用的語句為 System.Web.Http):

routes.MapHttpRoute(
  "ServiceDefault",
  "api/Customers/{id}",
  new { id = RouteParameter.Optional },
  null,
  new PHVAuthorizingMessageHandler());

而不是設置 InnerHandler 內 DelegatingHandler,你可以將 InnerHandler 屬性設置預設調度程式作為界定你的路線的一部分:

routes.MapHttpRoute(
  "ServiceDefault",
  "api/{controller}/{id}",
  new { id = RouteParameter.Optional },
  null,
  new PHVAuthorizingMessageHandler
  {InnerHandler = new HttpControllerDispatcher(
    GlobalConfiguration.Configuration)});

現在,而不您正在流傳多個 DelegatingHandlers 的 InnerHandler 設置,您管理它從單一位置在其中定義您的航線。

擴大校長

如果授權請求的名稱和作用是不夠的您可以通過創建您自己的主要類實現 IPrincipal 介面擴展的授權過程。 然而,若要利用的主要的自訂類,您需要創建您自己的自訂的授權屬性或將自訂代碼添加到您的服務方法。

例如,如果您有一套的只能從某一特定地區的使用者所訪問的服務,您可以創建一個簡單的主要類,實現 IPrincipal 介面和添加區域的屬性,如中所示圖 2

圖 2 使用附加屬性創建一個自訂主體

public class PHVPrincipal: IPrincipal
{
  public PHVPrincipal(string Name, string Region)
  {
    this.Name = Name;
    this.Region = Region;
  }
  public string Name { get; set; }
  public string Region { get; set; }
  public IIdentity Identity
  {
    get
    {
      return new GenericIdentity(this.Name);
    }
    set
    {
      this.Name = value.Name;
    }
   }
   public bool IsInRole(string role)
   {
     return true;
   }

要利用此新的主要類 (它將與任何主機的工作),你只需要來具現化它,然後使用它來設置 CurrentPrincipal 和使用者的屬性。 下面的代碼看起來與"區域"名稱關聯的請求的查詢字串中的值在檢索該值之後, 的代碼使用它通過將值傳遞給類的建構函式中設置了校長的區域屬性:

string region = (from kvp in request.GetQueryNameValuePairs()
                 where kvp.Key == "region"
                 select kvp.Value).FirstOrDefault();
Thread.CurrentPrincipal = new PHVPrincipal(userName, region);

如果您正在使用的 Microsoft.NET 框架 4.5,而不是實現 IPrincipal 介面,你應該從新的 ClaimsPrincipal 類繼承。 ClaimsPrincipal 支援基於索賠的處理和集成 Windows 身份基金會 (WIF)。 不過,亦是這篇文章 (會解決這一主題在即將條基於聲明的安全問題) 的範圍。

授權一個自訂主體

與一個新的主體物件中的地方,您可以創建一個授權屬性,利用了主體所攜帶的新資料。 首先,創建一個類的繼承自 System.Web.Http.AuthorizeAttri­弼和重寫其 IsAuthorized 方法 (這是不同的 pro­塞斯從ASP.NETMVC 實踐在其中創建新的授權屬性通過擴展 System.Web.Http.Filters.Autho­rizationFilterAttribute)。 IsAuthorized 方法傳遞 HttpActionCoNtext,其屬性可用作您的授權過程的一部分。 然而,本示例只需要從執行緒的 CurrentPrincipal 屬性中提取的主體物件,將它強制轉換為自訂的主要類型和檢查的區域屬性。 如果授權成功,代碼將返回 true。 如果授權失敗,你需要的 ActionCoNtext 回應屬性返回 false,如中所示之前創建自訂回應圖 3

圖 3 篩選自訂主體物件

public class RegionAuthorizeAttribute : System.Web.Http.AuthorizeAttribute
{
  public string Region { get; set; }
  protected override bool IsAuthorized(HttpActionContext actionContext)
  {
    PHVPrincipal phvPcp = Thread.CurrentPrincipal as PHVPrincipal;
    if (phvPcp != null && phvPcp.Region == this.Region)
    {
      return true;
    }
    else
    {
      actionContext.Response =
        new HttpResponseMessage(
          System.Net.HttpStatusCode.Unauthorized)
        {
          ReasonPhrase = "Invalid region"
        };
      return false;
    }        
  }
}

就像預設ASP.NET授權篩選器,可以使用您的自訂的授權篩選器。 因為此篩選器具有一個區域屬性,該屬性必須設置為可以接受的地區作為一部分的裝飾與它的服務方法此方法:

[RegionAuthorize(Region = "East")]
public HttpResponseMessage Get()
{

對於此示例,我選擇從授權繼承­因為我授權碼是純粹 CPU 綁定的屬性。 如果我的代碼需要訪問某些網路資源 (或都不做任何 I/O),會過更好的選擇,執行 IAuthorization­篩選器介面,因為它支援非同步調用。

正如我說這篇文章的開頭:典型的 Web API 方案不需要額外的授權,除了要保護免受捷克斯洛伐克聯邦共和國的漏洞。 但當你確實需要擴展預設安全系統,Web API 提供了許多選擇整個處理管道位置,您可以集成無論您需要的保護。 而且總是要選擇更好一些。

Peter Vogel 是在 ph 值 & V 資訊服務、 面向服務的體系結構、 XML、 資料庫和使用者介面設計方面的專門知識與專門從事ASP.NET開發的主體。

衷心感謝以下技術專家對本文的審閱:多米尼拜爾 (thinktecture GmbH & Co 公斤)、BarryDorrans (Microsoft) 和麥克華士信 (Microsoft)
邁克華士信 (mwasson@microsoft.com) 是在微軟的程式師-作家。 他目前正在寫ASP.NET,側重于 Web API。

BarryDorrans (Barry。Dorrans@microsoft.com) 是在微軟的 Azure 平臺團隊與工作安全開發人員。 他寫道"開始ASP.NET安全",並在加入微軟之前是開發人員安全最有價值球員。 儘管此他仍然會拼錯定期進行加密。

多米尼 (dominick.baier@thinktecture.com) 是 thinktecture 的安全顧問 (thinktecture.com)。 他的主要工作是在分散式應用程式的身份和存取控制,他是 IdentityModel 和 IdentityServer 的流行的開源專案的建立者。 他的部落格位於 leastprivilege.com