demoshop

demo, trying to be the best_

從 .NET Framework 時代的 ASP.NET MVC 就有內建防範 CSRF 的機制,這機制在 MVC 架構下開發者可自由的選擇啟用或關閉,但是 .NET Core 發行時新推出的 RazorPage 則是預設開啟了 CSRF 防範機制,現在的網路環境越來越危險了預設開啟是非常正確的選擇,但是這樣的預設啟用對於初級的開發者來說就會很挫折,為什麼一個 AJAX 都寫不好呢。

雖然說官方文件都有,但開發人員不看文件的比例和不寫文件差不多,所以我還是寫一篇來記錄一下吧

 

基本的 AJAX

首先來做一個基本的 HTML 用於稍等的 AJAX 應用。

<div class="text-center">
    <h1 class="display-4">SkillTree</h1>
    <div>
        <input id="a" name="a" value="test"/>
        <button type="button" data-url="@Url.Action("index")">Go</button>
    </div>
</div>

稍微注意的是上面的 HTML 並沒有 Form 標籤,而那個 Button 也真的是 Button ,實際的表單送出完全要依賴 AJAX 處理,下方就是範例 Code

<script>
    $(function() {
        $("[data-url]").on("click", function () {
            let targetUrl = $(this).data("url");
            $.ajax({
                type: "POST",
                url: targetUrl,
                data: "q="+$("#a").val(),
                dataType: "json",
                success: function (response) {
                    alert(response.test);
                }
            });    
        });
    
    });
</script>

當使用者按下 Go 按鈕就會觸發上述這段 Code 而把表單資料往 Controller 送  

[HttpPost]
public IActionResult Index(string q)
{
    return Json(new
                {
                    test=q
                });
}

執行後就會一切順利,本篇就可以結束了😏

包含 CSRF 防範的 AJAX

開頭有提到,MVC 架構下開發者可自由的選擇啟用或關閉(預設是關閉)所以剛剛的 Code 一切都正常,但當我們明確的加上 [ValidateAntiForgeryToken] 啟用 CSRF 防範機制後(Razor Page 預設就開啟防範機制)

[HttpPost]
[ValidateAntiForgeryToken]
public IActionResult Index(string q)
{
    return Json(new
                {
                    test=q
                });
}

一切都不同了,原本正常的程式,怎麼按都只能收到 400 的回應😱

為了安全,千萬絕對不可以閉上眼睛關閉驗證!

解決方式是首先要先讓頁面可以產生 Anti Forgery Token ,產生的 Code 都已經包裝在 @Html.AntiForgeryToken() Html Helper 內,直接使用即可,所以一開始的 HTML 變成這樣

<div class="text-center">
    <h1 class="display-4">SkillTree</h1>
    <div>
        <input id="a" name="a" value="test"/>
        <button type="button" data-url="@Url.Action("index")">Go</button>
    </div>
</div>
@Html.AntiForgeryToken()

 然後再把呼叫的 javascript 調整一下

<script>
    $(function() {
        $("[data-url]").on("click", function () {
            let targetUrl = $(this).data("url");
            $.ajax({
                type: "POST",
                url: targetUrl,
                data: "q="+$("#a").val(),
                dataType: "json",
                beforeSend: function (xhr) {
                    xhr.setRequestHeader("requestverificationtoken",
                        $('input:hidden[name="__RequestVerificationToken"]').val());
                },
                success: function (response) {
                    alert(response.test);
                }
            });    
        });
    
    });
</script>

網路有很多範例會需要加上 services.AddAntiforgery(o => o.HeaderName = "CSRFTOKEN"); 來調整 token 的名稱,但 demo 是推薦直接使用 requestverificationtoken 這樣就啥都不用再調了。

以上的解法也可使用在 Razor Page 內。

回應討論