[水煮 ASP.NET Web API2 方法論](1-7)CSRF-Cross-Site Request Forgery

問題javascript

  經過 CSRFCross-Site Request Forgery)防禦,保護從 MVC 頁面提交到ASP.NET Web API 的數據。html

 

解決方案java

  ASP.NET 已經加入了 CSRF 防禦功能,只要經過 System.web.Helpers.AntiForgery 類(System.Web.WebPages 的一部分)就能夠。web

他會生成兩個 Tokenajax

  • Cookie Tokenjson

  • 基於字符串的 Tokenc#

  基於字符串的 Token 是能夠嵌入到表單或者請求頭(使用 Ajax 的狀況下)。爲了防止 CSRF 攻擊,表單提交和Ajax 請求到 API 的數據必須包含這些Token,服務器將會驗證這兩個 Tokenapi

  在 ASP.NET Web APIanti-CSRF Token 驗證是一個典型的實現了橫切關係的 MessageHandler服務器

 

工做原理cookie

  爲了能在 MVC 應用程序的上下文中生成 Token,咱們必須在表單中調用一個叫作 AntiForgeryToken HtmlHelper 的擴展方法。

 

1
2
3
4
< form  id = "myForm" >
     @Html.AntiForgeryToken()
     @* 其餘標籤 *@
</ form >

 

  這個幫助方法在AntiForgery 類中。他會寫一個 Token 到響應的 Cookie 中,同時生成一個名字叫作_RequestVerificationToken 的字段,也會隨着表單數據同時被提交。

  爲能在服務器端驗證 Token,咱們能夠經過調用AntiForgery 類的靜態方法 Validate 來驗證。若是調用的時候沒有傳遞參數的話,就會從 HttpContext.Current 中試着獲取相關的 Cookie 和請求體中提取 Token,在這裏,咱們假設確實有一個 Body 而且 Body 中也有一個 _RequestVerificationToken

  因爲這個方法是 void (無返回值)的,因此,請求驗證成功後,方法什麼反饋也沒有,若是失敗,就會拋HttpAntiForgeryException 的異常。咱們能夠捕獲這個異常,而後返回給客戶端相應的響應(例如,一個 HTTP 403 的狀態碼)。

  有一個可替代的方式就是調用 Validate 方法,咱們本身來傳這兩個 Token。這時候,就要從 Request 中獲取這兩個值。例如,多是在 Header 中。這種方式也能夠擺脫對 HttpContext 的依賴。

  對於 Web API,咱們能夠自定義消息處理器,在每一個請求進入 Web API 的時候來負責 CSRF Token 的驗證,執行必要的驗證,而後繼續管道執行,或者,在請求無效的狀況下,直接短路錯誤響應(也就是說,當即返回錯誤碼)。

 

代碼演示

  咱們來演示 MessageHandler 執行 CSRF 驗證的例子如清單 1-23 所示。

  兩種方式:

  1.  Ajax 請求。

  2. 用其餘的請求。

  咱們都簡單假設他們都是表單提交的。若是是一個 Ajax 請求,咱們能夠嘗試着從請求 Header 中獲取Token,同時,能夠從與 Request 一同提交的 Cookie 集合中獲取Cookie Token,而後,使用無參的 Validate方法驗證,這樣,就須要咱們本身來提取 Token

  若是驗證失敗,客戶端會獲得一個 403 的錯誤響應。

 

清單 1-23. Anti_CSRF 消息處理器

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
public  class  AntiForgeryHandler : DelegatingHandler
{
     protected  override  async Task<HttpResponseMessage> SendAsync(
     HttpRequestMessage request,
     CancellationToken cancellationToken)
     {
         string  cookieToken =  null ;
         string  formToken =  null ;
         if  (request.IsAjaxRequest())
         {
             IEnumerable< string > tokenHeaders;
             if  (request.Headers.TryGetValues( "__RequestVerificationToken" out  tokenHeaders))
             {
                 var  cookie = request.Headers.GetCookies(AntiForgeryConfig.CookieName).
                 FirstOrDefault();
                 if  (cookie !=  null )
                 {
                     cookieToken = cookie[AntiForgeryConfig.CookieName].Value;
                 }
                 formToken = tokenHeaders.FirstOrDefault();
             }
         }
         try
         {
             if  (cookieToken !=  null  && formToken !=  null )
             {
                 AntiForgery.Validate(cookieToken, formToken);
             }
             else
             {
                 AntiForgery.Validate();
             }
         }
         catch  (HttpAntiForgeryException)
         {
             return  request.CreateResponse(HttpStatusCode.Forbidden);
         }
         return  await  base .SendAsync(request, cancellationToken);
     }
}

 

  咱們還須要在 API HttpConfiguration 中註冊,這樣纔會在全局起做用。

 

1
config.MessageHandlers.Add( new  AntiForgeryHandler());

 

  構築一個 anti-CSRF 護盾做爲消息處理器並非惟一方式。咱們也能夠在過濾器內部使用一樣的代碼,而後將過濾器應用到相應的 Action 上(相似的,怎麼用過濾器驗證,咱們將在 5-4 詳細討論)。若是消息處理器不是全局使用,也能夠附加到指定路由上。咱們將在 3-9 詳細討論這一起。

  HttpRequestMessage有一個內建的方式來檢查是否爲 Ajax 請求,就是用一個簡單的擴展方法來實現,他依賴於 Header  X-Requested-With,大多數的 JavaScript 框架都會自動發送這個在 Header 中。這個方法如清單1-24 所示。

 

清單 1-24. 檢查 HttpRequestMessage 是否爲一個 Ajax 請求的擴展方法。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
public  static  class  HttpRequestMessageExtensions
{
     public  static  bool  IsAjaxRequest( this  HttpRequestMessage request)
     {
         IEnumerable< string > headers;
         if  (request.Headers.TryGetValues( "X-Requested-With" out  headers))
         {
             var  header = headers.FirstOrDefault();
             if  (! string .IsNullOrEmpty(header))
             {
                 return  header.ToLowerInvariant() ==  "xmlhttprequest" ;
             }
         }
         return  false ;
     }
}

 

  清單 1-25 展現了,傳統表單提交和 Ajax 請求都利用 anti-CSRF Token 的例子。在傳統表單提交的狀況下,HTML helper 會生成一個隱藏域,同時,anti-forgery token 會隨着表單一起被自動提交。在 Ajax 請求的狀況下,咱們顯示的從隱藏域中讀取 Token,而後,將其附加到請求頭中。

 

清單 1-25. 傳統表單和 Ajax 請求方式下,提交數據到 ASP.NET WEB API 使用 Anti-CSRF 防禦

//HTML表單

1
2
3
4
5
6
7
8
9
10
11
12
<form id= "form1"  method= "post"  action= "/api/form"  enctype= "application/x-www-form-urlencoded" >
     @Html.AntiForgeryToken()
     <div>
         <label  for = "name" >Name</label>
     </div>
     <div>
         <input type= "text"  name= "name"  value= "Some Name"  />
     </div>
     <div>
         <button id= "postData"  name= "postData" >Post form</button>
     </div>
</form>

 

// Ajax 表單

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
@Html.AntiForgeryToken()
< input  id = "itemJS"  type = "text"  disabled = "disabled"  name = "text"  value = "some text"  />
< div >
     < button  id = "postJS"  name = "postJS" >Post JS</ button >
</ div >
< script  type = "text/javascript" >
     $(function () {
         $("#postJS").on("click", function () {
             $.ajax({
                 dataType: "json",
                 data: JSON.stringify({ name: $("#itemJS").val() }),
                 type: "POST",
                 headers: {
                     "__RequestVerificationToken": $("#jsData input[name='__
                     RequestVerificationToken']").val()
                 },
                 contentType: "application/json; charset=utf-8",
                 url: "/api/items"
             }).done(function (res) {
                 alert(res.Name);
             });
         });
     });
</ script >
相關文章
相關標籤/搜索