我有一个单页面应用程序(用户加载一堆HTML / JS,然后在没有另外调用MVC的情况下发出AJAX请求 - 仅通过WebAPI)。在WebAPI中,我有以下内容:
public sealed class WebApiValidateAntiForgeryTokenAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(
System.Web.Http.Controllers.HttpActionContext actionContext)
{
if (actionContext == null)
{
throw new ArgumentNullException(nameof(actionContext));
}
if (actionContext.Request.Method.Method == "POST")
{
string requestUri = actionContext.Request.RequestUri.AbsoluteUri.ToLower();
if (uriExclusions.All(s => !requestUri.Contains(s, StringComparison.OrdinalIgnoreCase))) // place some exclusions here if needed
{
HttpRequestHeaders headers = actionContext.Request.Headers;
CookieState tokenCookie = headers
.GetCookies()
.Select(c => c[AntiForgeryConfig.CookieName]) // __RequestVerificationToken
.FirstOrDefault();
string tokenHeader = string.Empty;
if (headers.Contains("X-XSRF-Token"))
{
tokenHeader = headers.GetValues("X-XSRF-Token").FirstOrDefault();
}
AntiForgery.Validate(!string.IsNullOrEmpty(tokenCookie?.Value) ? tokenCookie.Value : null, tokenHeader);
}
}
base.OnActionExecuting(actionContext); // this is where it throws
}
}
在Global.asax注册:
private static void RegisterWebApiFilters(HttpFilterCollection filters)
{
filters.Add(new WebApiValidateAntiForgeryTokenAttribute());
filters.Add(new AddCustomHeaderFilter());
}
有时,我在日志中看到The anti-forgery cookie token and form field token do not match
错误。发生这种情况时,tokenCookie.value
和tokenHeader
都不为空。
Clientside,我的所有AJAX请求都使用以下内容:
beforeSend: function (request) {
request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
},
Razor在我的SPA页面上生成一次令牌:
@Html.AntiForgeryToken()
我在Web.config中设置了我的机器密钥。
导致这种情况的原因是什么?
更新 我刚检查了日志,我有时也看到了这个:
提供的防伪令牌适用于用户"",但当前用户是#34; someuser@domain.com"。几秒钟之前
当用户在登录时刷新他们的SPA实例时会发生这种情况。由于某种原因,SPA会将其放入登录页面而不是内页(User.Identity.IsAuthenticated
为真) - 然后他们可以&#39 ; t因为此错误而登录。清爽将它们拉回来。不确定这意味着什么,但我认为更多的信息不会受到伤害。
答案 0 :(得分:3)
我的回答是建议不要尝试在AJAX调用中使用基于令牌的CSRF保护,而是依赖于Web浏览器的原生CORS功能。
基本上,从浏览器到后端服务器的任何AJAX调用都将检查域来源(也就是加载脚本的域)。如果域匹配(JS托管域==目标AJAX服务器域),则AJAX调用执行正常,否则返回null
。
如果攻击者试图在自己的服务器上托管恶意AJAX查询,如果您的后端服务器没有允许他这样做的CORS策略,则会失败(默认情况下就是如此)。
因此,在本地, CSRF保护在AJAX调用中无用,您可以通过简单地不尝试处理来降低技术债务。
的更多信息代码示例 - 使用您的控制台检查器!
<html>
<script>
function reqListener () {
console.log(this.responseText);
}
var oReq = new XMLHttpRequest();
oReq.addEventListener("load", reqListener);
oReq.open("GET", "http://www.reuters.com/");
oReq.send();
</script>
</html>
运行它并查看安全错误:
阻止跨源请求:同源策略禁止读取 http://www.reuters.com/处的远程资源。 (原因:CORS标题 “Access-Control-Allow-Origin”缺失。)
Mozilla对Cross-site XMLHttpRequest实施非常清楚:
现代浏览器通过实施Web支持跨站点请求 应用程序(WebApps)工作组的跨站点访问控制 请求标准。
只要服务器配置为允许来自您网络的请求 应用程序的起源,XMLHttpRequest将起作用。否则,一个 抛出INVALID_ACCESS_ERR异常。
答案 1 :(得分:0)
我试着给出一个相同的答案,如果在我们交换的评论中,你的看起来与我的相关情况似乎不是......
此类问题可归因于XMLHttpRequest.setRequestHeader()
行为,因为此功能&#34; combines&#34;已经在http请求的上下文中分配的标头的值,如MDN和Whatwg所述:
如果使用相同的标题多次调用此方法,则 值合并为一个请求标头。
所以,如果我们有SPA
例如执行所有ajax POSTs
设置给定的http标头,在你的情况下:
beforeSend: function (request) {
request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}
第一个ajax POST
请求设置了一个明确的标题("X-XSRF-Token"
),因此,在服务器端,你应该有一个&#34;有效的&#34;要与之比较的标头值。
但是,如果没有页面刷新或新的GET
请求,所有后续的ajax POSTs
以及MDN和Whatwg文档中都会说明,将对同一标题("X-XSRF-Token"
)进行脏分配,因为它们combine新旧值的值。
要避免此问题,您可以尝试重置"X-XSRF-Token"
值(但是没有太多文档,这似乎是一个不可靠的解决方案......)
beforeSend: function (request) {
request.setRequestHeader("X-XSRF-Token", null); //depends on user agents..
//OR.. request.setRequestHeader("X-XSRF-Token", ''); //other user agents..
//OR.. request.setRequestHeader("X-XSRF-Token"); //other user agents..
request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}
其他解决方案可以依赖于您必须自己实现的某些客户端状态处理机制,因为无法获取http请求标头的值或状态访问权限(只能访问响应标头)。 / p>
更新 - 以下文字的修订版:
因此,如果我们有SPA
例如执行所有ajax POSTs
为每次调用回收XMLHttpRequest对象并设置给定的http标头,在您的情况下:
......