zoukankan      html  css  js  c++  java
  • [水煮 ASP.NET Web API2 方法论](1-7)CSRF-Cross-Site Request Forgery

    问题

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

    工作原理

      为了能在 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>
  • 相关阅读:
    简单读取winfrom资源文件
    string.Format对C#字符串格式化
    如何在SQL中使用循环结构
    Linq to SQL 多条件动态组合查询(实战篇)
    提问的艺术
    XtraGrid 单元格加边框颜色
    凭证控件制作
    C# double 四舍五入
    自定义光标样式
    触发窗体事件(例如按Esc关闭窗体)
  • 原文地址:https://www.cnblogs.com/shuizhucode/p/6068086.html
Copyright © 2011-2022 走看看