[水煮ASP.NETWebAPI2方法论](1-7)CSRF-Cross-SiteRequestForgery

问题

成都创新互联服务项目包括化德网站建设、化德网站制作、化德网页制作以及化德网络营销策划等。多年来,我们专注于互联网行业,利用自身积累的技术优势、行业经验、深度合作伙伴关系等,向广大中小型企业、政府机构等提供互联网行业的解决方案,化德网站推广取得了明显的社会效益与经济效益。目前,我们服务的客户以成都为中心已经辐射到化德省份的部分城市,未来相信会继续扩大服务区域并继续获得客户的支持与信任!

  通过CSRF(Cross-Site Request 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-CSRF Token验证是一个典型的实现了横切关系的 MessageHandler。

 

工作原理

  为了能在MVC应用程序的上下文中生成 Token,我们必须在表单中调用一个叫做 AntiForgeryToken的 HtmlHelper的扩展方法。

 


    @Html.AntiForgeryToken()
    @* 其他标签 *@

 

  这个帮助方法在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 消息处理器

public class AntiForgeryHandler : DelegatingHandler
{
    protected override async Task SendAsync(
    HttpRequestMessage request,
    CancellationToken cancellationToken)
    {
        string cookieToken = null;
        string formToken = null;
        if (request.IsAjaxRequest())
        {
            IEnumerable 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中注册,这样才会在全局起作用。

 

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 请求的扩展方法。

public static class HttpRequestMessageExtensions
{
    public static bool IsAjaxRequest(this HttpRequestMessage request)
    {
        IEnumerable 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表单


    @Html.AntiForgeryToken()
    
        Name     
    
             
    
        Post form     

 

//Ajax表单

@Html.AntiForgeryToken()

    Post JS
    $(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);             });         });     });

文章标题:[水煮ASP.NETWebAPI2方法论](1-7)CSRF-Cross-SiteRequestForgery
文章起源:http://pwwzsj.com/article/geiegj.html