[水煮 ASP.NET Web API2 方法论](1-7)CSRF-Cross-Site Request Forgery
问题
通过 CSRF(Cross-SiteRequest Forgery)防护,保护从 MVC 页面提交到ASP.NET Web API 的数据。
解决方案
ASP.NET 已经加入了 CSRF 防护功能,只要通过 System.web.Helpers.AntiForgery 类(System.Web.WebPages 的一部分)就可以。
他会生成两个 Token:
Cookie Token
基于字符串的 Token
基于字符串的 Token 是可以嵌入到表单或者请求头(使用 Ajax 的情况下)。为了防止 CSRF ***,表单提交和Ajax 请求到 API 的数据必须包含这些Token,服务器将会验证这两个 Token。
在 ASP.NET Web API,anti-CSRFToken 验证是一个典型的实现了横切关系的 MessageHandler。
工作原理
为了能在 MVC 应用程序的上下文中生成 Token,我们必须在表单中调用一个叫做 AntiForgeryToken 的 HtmlHelper 的扩展方法。
<formid="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 所示。
两种方式:
用 Ajax 请求。
用其他的请求。
我们都简单假设他们都是表单提交的。如果是一个 Ajax 请求,我们可以尝试着从请求 Header 中获取 Token,同时,可以从与 Request 一同提交的 Cookie 集合中获取Cookie Token,然后,使用无参的 Validate 方法验证,这样,就需要我们自己来提取 Token。
如果验证失败,客户端会得到一个 403 的错误响应。
清单 1-23. Anti_CSRF 消息处理器
publicclassAntiForgeryHandler:DelegatingHandler{protectedoverrideasyncTask<HttpResponseMessage>SendAsync(HttpRequestMessagerequest,CancellationTokencancellationToken){stringcookieToken=null;stringformToken=null;if(request.IsAjaxRequest()){IEnumerable<string>tokenHeaders;if(request.Headers.TryGetValues("__RequestVerificationToken",outtokenHeaders)){varcookie=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){returnrequest.CreateResponse(HttpStatusCode.Forbidden);}returnawaitbase.SendAsync(request,cancellationToken);}}
我们还需要在 API 的HttpConfiguration 中注册,这样才会在全局起作用。
config.MessageHandlers.Add(newAntiForgeryHandler());
构筑一个 anti-CSRF 护盾作为消息处理器并不是唯一方式。我们也可以在过滤器内部使用同样的代码,然后将过滤器应用到相应的 Action 上(类似的,怎么用过滤器验证,我们将在 5-4 详细讨论)。如果消息处理器不是全局使用,也可以附加到指定路由上。我们将在 3-9 详细讨论这一块儿。
HttpRequestMessage有一个内建的方式来检查是否为 Ajax 请求,就是用一个简单的扩展方法来实现,他依赖于 Header 的 X-Requested-With,大多数的 JavaScript 框架都会自动发送这个在 Header 中。这个方法如清单1-24 所示。
清单 1-24. 检查 HttpRequestMessage 是否为一个Ajax 请求的扩展方法。
publicstaticclassHttpRequestMessageExtensions{publicstaticboolIsAjaxRequest(thisHttpRequestMessagerequest){IEnumerable<string>headers;if(request.Headers.TryGetValues("X-Requested-With",outheaders)){varheader=headers.FirstOrDefault();if(!string.IsNullOrEmpty(header)){returnheader.ToLowerInvariant()=="xmlhttprequest";}}returnfalse;}}
清单 1-25 展示了,传统表单提交和 Ajax 请求都利用 anti-CSRF Token 的例子。在传统表单提交的情况下,HTMLhelper 会生成一个隐藏域,同时,anti-forgery token 会随着表单一块儿被自动提交。在 Ajax 请求的情况下,我们显示的从隐藏域中读取 Token,然后,将其附加到请求头中。
清单 1-25. 传统表单和 Ajax 请求方式下,提交数据到ASP.NET WEB API 使用 Anti-CSRF 防护
//HTML表单
<formid="form1"method="post"action="/api/form"enctype="application/x-www-form-urlencoded">@Html.AntiForgeryToken()<div><labelfor="name">Name</label></div><div><inputtype="text"name="name"value="SomeName"/></div><div><buttonid="postData"name="postData">Postform</button></div></form>
// Ajax 表单
@Html.AntiForgeryToken()<inputid="itemJS"type="text"disabled="disabled"name="text"value="sometext"/><div><buttonid="postJS"name="postJS">PostJS</button></div><scripttype="text/javascript">$(function(){$("#postJS").on("click",function(){$.ajax({dataType:"json",data:JSON.stringify({name:$("#itemJS").val()}),type:"POST",headers:{"__RequestVerificationToken":$("#jsDatainput[name='__RequestVerificationToken']").val()},contentType:"application/json;charset=utf-8",url:"/api/items"}).done(function(res){alert(res.Name);});});});</script>
声明:本站所有文章资源内容,如无特殊说明或标注,均为采集网络资源。如若本站内容侵犯了原著者的合法权益,可联系本站删除。