Ajax 搭配 AntiForgeryToken

 

一、

為了防範 Cross-site request forgery 問題(簡稱 CSRF or XSRF),

ASP.NET MVC 已經提供了 AntiForgeryToken、ValidateAntiForgeryToken 可用,範例如下

HomeController.cs

using System.Web.Mvc;

namespace WebApplication1.Controllers
{
    public class HomeController : Controller
    {
        private static string _userId = "";
        public ActionResult Index()
        {
            return View();
        }

        [ValidateAntiForgeryToken]
        [HttpPost]
        public ActionResult Index(string userId)
        {
            _userId = userId;
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.UserId = _userId;

            return View();
        }
    }
}

 

Index.cshtml

@using (Html.BeginForm("Index", "Home", null, FormMethod.Post))
{
    @Html.AntiForgeryToken()
    <label>請輸入你的名字</label>
    <input type="text" name="userId" value="" />
    <input type="submit" value="送出" />
}

當 submit 時,request header 就會夾帶 __RequestVerificationToken,

並將之一併傳送到後端做驗證。

 

Contact.cshtml

<p>你輸入的名字為 @ViewBag.UserId</p>

 

二、

至於採用 Ajax 技術方面也有方法可處理 CSRF,範例如下

檔案結構為

 

HomeController.cs 為

using System.Web.Mvc;
using WebApplication1.Filters;

namespace WebApplication1.Controllers
{
    public class HomeController : Controller
    {
        private static string _userId = "";
        public ActionResult Index()
        {
            return View();
        }

        [AjaxValidateAntiForgeryToken]
        [HttpPost]
        public ActionResult Index(string userId)
        {
            _userId = userId;
            return View();
        }

        public ActionResult About()
        {
            ViewBag.Message = "Your application description page.";

            return View();
        }

        public ActionResult Contact()
        {
            ViewBag.UserId = _userId;

            return View();
        }
    }
}

 

AjaxValidateAntiForgeryTokenAttribute.cs 為

using System;
using System.Web;
using System.Web.Helpers;
using System.Web.Mvc;

namespace WebApplication1.Filters
{
    public class AjaxValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
    {
        public void OnAuthorization(AuthorizationContext filterContext)
        {
            try
            {
                if (filterContext.HttpContext.Request.IsAjaxRequest())
                {
                    ValidateRequestHeader(filterContext.HttpContext.Request);
                }
                else
                {
                    filterContext.HttpContext.Response.StatusCode = 404;
                    filterContext.Result = new HttpNotFoundResult();
                }
            }
            catch (HttpAntiForgeryException e)
            {
                throw new HttpAntiForgeryException("Anti forgery token cookie not found");
            }
        }

        private void ValidateRequestHeader(HttpRequestBase request)
        {
            String cookieToken = String.Empty;
            String formToken = String.Empty;
            String TokenValue = request.Headers["RequestVerificationToken"];

            if (!String.IsNullOrWhiteSpace(TokenValue))
            {
                String[] Tokens = TokenValue.Split(new string[] { "@.@" }, StringSplitOptions.RemoveEmptyEntries);
                if (Tokens.Length == 2)
                {
                    cookieToken = Tokens[0];
                    formToken = Tokens[1];
                }
                AntiForgery.Validate(cookieToken, formToken);
            }
        }
    }
}

有人會將非 Ajax Request 使其走 ValidateAntiForgeryToken,

而 Ajax Request 使其走自訂的 AjaxValidateAntiForgeryToken,

我覺得可以看情形,不一定就要跟他人一樣一率拆開,

合在一起或拆開,先看情形,在用最適合的方式。

 

index.cshtml 為


<label>請輸入你的名字</label>
<input type="text" id="userId" name="userId" value="" />
<button id="mybutton">送出</button>


@functions{
    public static string GetAntiForgery()
    {
        string cookieToken, formToken;
        AntiForgery.GetTokens(null, out cookieToken, out formToken);
        return String.Concat(cookieToken, "@.@", formToken);
    }
}

@section scripts{
    <script>
        document.getElementById("mybutton").addEventListener("click", loadDoc);
        function loadDoc() {
            var userId = document.getElementById("userId").value;

            var xhttp = new XMLHttpRequest();//宣告XMLHttpRequest物件
            xhttp.onreadystatechange = function () {//如果readyState有被改變時
                if (this.readyState == 4 && this.status == 200) {
                    //從後端回應的responseText內容放至result裡面
                    //document.getElementById("result").innerHTML = this.responseText;
                }
            };
            xhttp.open("POST", "@Url.Action("Index")", true);
            xhttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
            xhttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
            xhttp.setRequestHeader("RequestVerificationToken", "@GetAntiForgery()");

            xhttp.send("userId=" + userId);
        }
    </script>
}

 

Contact.cshtml 為

<p>你輸入的名字為 @ViewBag.UserId</p>

 

參考資料:

Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core

ASP.NET MVC 防範 CSRF 攻擊 - 在 AJAX 裡使用 AntiForgeryToken 的處理