文章出處

問題

  通過 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>

文章列表




Avast logo

Avast 防毒軟體已檢查此封電子郵件的病毒。
www.avast.com


arrow
arrow
    全站熱搜
    創作者介紹
    創作者 大師兄 的頭像
    大師兄

    IT工程師數位筆記本

    大師兄 發表在 痞客邦 留言(0) 人氣()