ASP.NET MVC中AJAX请求传递AntiForgeryToken失败如何解决?
问题场景
在ASP.NET MVC的Index视图中,列表每条数据都有删除用的ActionLink,URL格式为/Controller/Delete/id。点击链接弹出确认模态框后,通过AJAX发送POST请求到带[HttpPost]和[ValidateAntiForgeryToken]特性的控制器Delete方法,但始终报错:
缺少必需的防伪表单字段“__RequestVerificationToken”。
控制器Delete方法代码:
[HttpPost] [ValidateAntiForgeryToken] public ActionResult Delete(int id) { try { Group g = db.Groups.Find(id); if (g != null) { db.Groups.Remove(g); db.SaveChanges(); var url = Url.Action("IndexPartial"); return Json(new { success = true, message = "删除成功", redirectUrl = url }); } else { return Json(new { success = false, message = "记录未找到" }); } } catch (Exception ex) { return Json(new { success = false, message = ex.Message.ToString() }); } }
视图已添加@Html.AntiForgeryToken(),AJAX请求代码:
$('#confirmActionBtn').on('click', function () { var token = $('[name="__RequestVerificationToken"]').val(); // 获取AntiForgeryToken值 $.ajax({ url: actionConfirmModal.attr("data-actionUrl"), type: 'POST', contentType: 'application/json', headers: {'RequestVerificationToken':token}, success: function (response) { setupModal('result'); if (response.success) { if (response.redirectUrl != null) { var targetPlaceHolder = actionConfirmModal.attr("data-targetPlaceHolder"); if (targetPlaceHolder != null) { targetPlaceHolder.load(redirectUrl); } else { window.location.href = redirectUrl; } } else { // 执行其他操作 } } else { $('#resultMessage').text("错误 : " + response.message); } }, error: function (xhr, status, error) { setupModal('result'); $('#resultMessage').text("错误 : " + error); }, }); });
问题原因
默认情况下,ASP.NET MVC的[ValidateAntiForgeryToken]特性只会从表单字段或请求的Form集合中读取__RequestVerificationToken,而当前代码把Token放在请求Header里,并且设置了contentType: 'application/json',导致后端无法识别到Token。
解决方案
方案1:将Token放入请求Body中(推荐)
修改AJAX代码,把Token和ID一起作为JSON数据发送,同时保留contentType: 'application/json':
$('#confirmActionBtn').on('click', function () { var token = $('[name="__RequestVerificationToken"]').val(); var actionUrl = actionConfirmModal.attr("data-actionUrl"); var id = actionUrl.split('/').pop(); // 从URL中提取id $.ajax({ url: actionUrl, type: 'POST', contentType: 'application/json', data: JSON.stringify({ __RequestVerificationToken: token, id: id }), success: function (response) { setupModal('result'); if (response.success) { if (response.redirectUrl != null) { var targetPlaceHolder = actionConfirmModal.attr("data-targetPlaceHolder"); if (targetPlaceHolder != null) { $(targetPlaceHolder).load(response.redirectUrl); // 修复原代码的变量引用错误 } else { window.location.href = response.redirectUrl; } } else { // 执行其他操作 } } else { $('#resultMessage').text("错误 : " + response.message); } }, error: function (xhr, status, error) { setupModal('result'); $('#resultMessage').text("错误 : " + error); }, }); });
方案2:自定义验证特性支持Header读取Token
如果不想修改前端,可以创建自定义的AntiForgeryToken验证特性,允许从Header中读取Token:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, AllowMultiple = false, Inherited = true)] public class ValidateAntiForgeryTokenFromHeaderAttribute : FilterAttribute, IAuthorizationFilter { public void OnAuthorization(AuthorizationContext filterContext) { var httpContext = filterContext.HttpContext; var token = httpContext.Request.Headers["RequestVerificationToken"]; if (string.IsNullOrEmpty(token)) { filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.BadRequest, "缺少防伪令牌"); return; } try { AntiForgery.Validate(httpContext.User.Identity.Name, token); } catch (AntiForgeryValidationException) { filterContext.Result = new HttpStatusCodeResult(HttpStatusCode.Forbidden, "防伪令牌验证失败"); } } }
然后将控制器上的[ValidateAntiForgeryToken]替换为[ValidateAntiForgeryTokenFromHeader]即可。
方案3:使用FormData格式发送请求
不设置contentType: 'application/json',改用FormData传递Token和ID:
$('#confirmActionBtn').on('click', function () { var token = $('[name="__RequestVerificationToken"]').val(); var actionUrl = actionConfirmModal.attr("data-actionUrl"); var id = actionUrl.split('/').pop(); var formData = new FormData(); formData.append('__RequestVerificationToken', token); formData.append('id', id); $.ajax({ url: actionUrl, type: 'POST', data: formData, processData: false, // 必须设置,避免jQuery错误处理FormData contentType: false, // 必须设置,让浏览器自动生成正确的Content-Type success: function (response) { setupModal('result'); if (response.success) { if (response.redirectUrl != null) { var targetPlaceHolder = actionConfirmModal.attr("data-targetPlaceHolder"); if (targetPlaceHolder != null) { $(targetPlaceHolder).load(response.redirectUrl); } else { window.location.href = response.redirectUrl; } } else { // 执行其他操作 } } else { $('#resultMessage').text("错误 : " + response.message); } }, error: function (xhr, status, error) { setupModal('result'); $('#resultMessage').text("错误 : " + error); }, }); });
注意:原AJAX代码中
targetPlaceHolder.load(redirectUrl)存在变量引用错误,需改为$(targetPlaceHolder).load(response.redirectUrl),因为redirectUrl是响应对象的属性而非全局变量。
内容的提问来源于stack exchange,提问作者Liuc

