public bool TryValidateTokenSet( HttpContext httpContext, AntiforgeryToken cookieToken, AntiforgeryToken requestToken, out string message) { //去掉了部分非空的判断 // Do the tokens have the correct format? if (!cookieToken.IsCookieToken || requestToken.IsCookieToken) { message = Resources.AntiforgeryToken_TokensSwapped; return false; } // Are the security tokens embedded in each incoming token identical? if (!object.Equals(cookieToken.SecurityToken, requestToken.SecurityToken)) { message = Resources.AntiforgeryToken_SecurityTokenMismatch; return false; } // Is the incoming token meant for the current user? var currentUsername = string.Empty; BinaryBlob currentClaimUid = null; var authenticatedIdentity = GetAuthenticatedIdentity(httpContext.User); if (authenticatedIdentity != null) { currentClaimUid = GetClaimUidBlob(_claimUidExtractor.ExtractClaimUid(httpContext.User)); if (currentClaimUid == null) { currentUsername = authenticatedIdentity.Name ?? string.Empty; } } // OpenID and other similar authentication schemes use URIs for the username. // These should be treated as case-sensitive. var comparer = StringComparer.OrdinalIgnoreCase; if (currentUsername.StartsWith("http://", StringComparison.OrdinalIgnoreCase) || currentUsername.StartsWith("https://", StringComparison.OrdinalIgnoreCase)) { comparer = StringComparer.Ordinal; } if (!comparer.Equals(requestToken.Username, currentUsername)) { message = Resources.FormatAntiforgeryToken_UsernameMismatch(requestToken.Username, currentUsername); return false; } if (!object.Equals(requestToken.ClaimUid, currentClaimUid)) { message = Resources.AntiforgeryToken_ClaimUidMismatch; return false; } // Is the AdditionalData valid? if (_additionalDataProvider != null && !_additionalDataProvider.ValidateAdditionalData(httpContext, requestToken.AdditionalData)) { message = Resources.AntiforgeryToken_AdditionalDataCheckFailed; return false; } message = null; return true; }
注:验证前还有一个反序列化的过程,这个反序列化就是从Cookie中拿到要判断的cookietoken和requesttoken
如何使用
前面粗略介绍了一下其内部的实现,下面再用个简单的例子来看看具体的使用情况:
使用一:常规的Form表单
先在视图添加一个Form表单
<form action="/home/antiform" method="post"> @Html.AntiForgeryToken() <p><input type="text" /></p> <p><input type="submit" value="Send by Form" /></p> </form>
在控制器添加一个Action
[ValidateAntiForgeryToken] [HttpPost] public IActionResult AntiForm(string message) { return Content(message); }
来看看生成的html是不是如我们前面所说,将@Html.AntiForgeryToken()输出为一个name为__RequestVerificationToken的隐藏域:
再来看看cookie的相关信息:
可以看到,一切都还是按照前面所说的执行。在输入框输入信息并点击按钮也能正常显示我们输入的文字。
使用二:Ajax提交
表单:
<form action="/home/antiajax" method="post"> @Html.AntiForgeryToken() <p><input type="text" /></p> <p><input type="button" value="Send by Ajax" /></p> </form>
js:
$(function () { $("#btnAjax").on("click", function () { $("#form2").submit(); }); })
这样子的写法也是和上面的结果是一样的!
怕的是出现下面这样的写法:
$.ajax({ type: "post", dataType: "html", url: '@Url.Action("AntiAjax", "Home")', data: { message: $('#ajaxMsg').val() }, success: function (result) { alert(result); }, error: function (err, scnd) { alert(err.statusText); } });
这样,正常情况下确实是看不出任何毛病,但是实际确是下面的结果(400错误):
相信大家也都发现了问题的所在了!!隐藏域的相关内容并没有一起post过去!!
处理方法有两种:
方法一:
在data中加上隐藏域相关的内容,大致如下:
$.ajax({ // data: { message: $('#ajaxMsg').val(), __RequestVerificationToken: $("input[name='__RequestVerificationToken']").val()} });
方法二:
在请求中添加一个header