在 AJAX 裡使用 AntiForgeryToken - (MVC)

  • 6582
  • 0
  • MVC
  • 2019-06-26

在 AJAX 裡使用 AntiForgeryToken - (ASP.NET MVC 防範 CSRF 攻擊)

在MVC的架構底下,要防止CSRF 攻擊可以在檢視頁面上加入使用 AntiFrogeryToken,並且在後端所對應的 Action 方法加上Attribute [ValidateAntiForgeryToken]

作法很簡單,但是如果我今天想要使用Ajax去跟後端互動,該如何使用AntiFrogeryToken

在網路上參考 MRKT大的這篇文章,以下把內容實作出來並紀錄一下。

 

1.在專案根目錄下建立「App_Code」目錄,並且新增 CommonRazorFunctions.cshtml 檢視檔案

 

2.在 CommonRazorFunctions.cshtml 裡加入以下的 Razor @functions

透過AntiForgery.GetTokens產生兩個token並組合再一起。

3.在HomeController底下加入Action以及相對應的View

HomeController:

/// <summary>
/// 第一次Get的時候呼叫
/// </summary>
/// <returns></returns>
public ActionResult CheckAccount()
{
	return View();
}

/// <summary>
/// 透過畫面上的Submit button 觸發Submit事件呼叫
/// </summary>
/// <param name="collection"></param>
/// <returns></returns>
[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult CheckAccount(FormCollection collection)
{
	TempData["lastName"] = collection["txtAccount"];
	return View();
}

/// <summary>
/// 透過AJAX呼叫對應的Action,可看到這個Action 加了Attribute:AjaxValidateAntiForgeryToken
/// 檢查輸入的LastName是否有重複存在Table:Employees 內
/// </summary>
/// <param name="strLastName"></param>
/// <returns></returns>
[HttpPost]
[AjaxValidateAntiForgeryToken]
public ActionResult CheckAcountByAjax(string strLastName)
{
	bool Exist = (from p in db.Employees.AsQueryable() where p.LastName == strLastName select p).Any();

	return Json(Exist);
}

View:

View的部分我做了兩顆按鈕,一個是測試ajax用,一個是測試Submit用,

我希望達到的效果是Ajax 可以使用 AntiFrogeryToken,也不影響原有的submit button的機制

Scripts 的部分,主要是要加入headers

<script type="text/javascript">
	$(function () {
		$('#btnCheck').on('click', function () {
			var _Object = {
				strLastName: $.trim($('#txtAccount').val())
			};
			
			$.ajax({
				type: "POST",
				url: '@Url.Action("CheckAcountByAjax", "Home")', 
				data: JSON.stringify(_Object),
				async: false,
				contentType: "application/json; charset=utf-8",
				dataType: "json",
				headers:{
					//加入 Request Header,這裡就會使用到在一開始所建立的 GetAntiForgeryToken()
					'RequestVerificationToken':'@CommonRazorFunctions.GetAntiForgery()'
				},
				success: function (response) {                        
					if(response){
						alert('LastName is Exist');
					}
					else{
						alert('LastName not Exist');
					}
				},
				error: function (error) {
					alert('error: ' + error);
				}
			});
		});
	});
</script>

後端對應的Action:CheckAcountByAjax 已經加上[AjaxValidateAntiForgeryToken]

來看一下這一段要怎麼寫:

 

在網站專案根目錄下的 Filters 目錄裡新增「AjaxValidateAntiForgeryTokenAttribute.cs」

裡面的code:

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AjaxValidateAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
{
	public void OnAuthorization(AuthorizationContext filterContext)
	{
		try
		{
			//只有Ajax 才處理
			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");
		}
	}

	/// <summary>
	/// 解析前端丟過來的Token 是否正確
	/// </summary>
	/// <param name="request"></param>
	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(':');
			if (Tokens.Length == 2)
			{
				cookieToken = Tokens[0];
				formToken = Tokens[1];
			}
			AntiForgery.Validate(cookieToken, formToken);
		}
	}
}

通通完成之後,將專案On起來,打開F12,可看到由 Razor @funtions「GetAntiForgeryToken()」所產生的 Token

測試Ajax的Click事件時,可以看到 Request Header的內容

在後端的程式碼下中斷點,可以看到取得的Token,

將Token分為cookieToken 與 formToken 後再使用 AntiForgery.Validate() 驗證前端所送過來的 Token 是否正確。

以上就是如何在 jQuery的 AJAX 事件內裡使用 AntiForgeryToken \。

 

PS:根據MRKT大的文章:如果將頁面上的 Javascript 程式抽出為 JS 檔案,則上面的作法就不適用了,在此特別說明。

在找時間測試一下。