[水煮 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 >
相关文章
相关标签/搜索