使用 WebApi 时防伪 cookie 令牌和表单字段令牌不匹配
The anti-forgery cookie token and form field token do not match when using WebApi
我有一个单页应用程序(用户加载一堆 HTML/JS 然后发出 AJAX 请求而无需再次调用 MVC - 仅通过 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
都不为空。
客户端,我的所有 AJAX 请求都使用以下内容:
beforeSend: function (request) {
request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
},
使用 Razor 在我的 SPA 页面上生成一次令牌:
@Html.AntiForgeryToken()
我在 Web.config 中设置了我的机器密钥。
可能是什么原因造成的?
更新
我刚刚检查了日志,有时我也看到了这个:
The provided anti-forgery token was meant for user "", but the current user is "someuser@domain.com". a few seconds ago
当用户在登录时刷新他们的 SPA 实例时会发生这种情况。然后 SPA 出于某种原因将它们放入登录页面而不是内页(User.Identity.IsAuthenticated
为真)- 然后他们可以' 因为这个错误而登录。提神将它们拉回内部。不确定这意味着什么,但我认为更多信息不会造成伤害。
我试着给出同样的答案,如果在我们交换的评论中,你的情况似乎与我的不相关..
此类问题可能是由 XMLHttpRequest.setRequestHeader()
行为引起的,因为此函数“combines" the values of an header that has been already assigned in the context of an http request, as stated by MDN and Whatwg:
If this method is called several times with the same header, the
values are merged into one single request header.
因此,如果我们有一个 SPA
例如执行所有 ajax POSTs
设置给定的 http header,在你的情况下:
beforeSend: function (request) {
request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}
第一个 ajax POST
请求设置了一个明确的 header ("X-XSRF-Token"
) 所以,服务器端,你应该有一个 "valid" header 要比较的值。
但是,如果没有页面刷新或新的 GET
请求,所有后续 ajax POSTs
,以及 MDN and Whatwg documentation, will make a dirty assignment of the same header ("X-XSRF-Token"
), because they 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"););
}
其他解决方案可以依赖于您必须自己实现的某些 client-side 状态处理机制,因为无法获取 http 请求的值或状态访问 headers (只能访问响应headers)。
更新 - 修订以下文字:
因此,如果我们有一个 SPA
例如执行所有 ajax POSTs
回收 XMLHttpRequest object 每次调用和设置给定的 http header,在你案件:
...
我的回答是建议不要尝试在 AJAX 调用中使用基于令牌的 CSRF 保护,而是依赖网络浏览器的原生 CORS 功能。
基本上,从浏览器到后端服务器的任何 AJAX 调用都会检查域来源(也就是从中加载脚本的域)。如果域匹配(JS 托管域 == 目标 AJAX 服务器域),AJAX 调用执行正常,否则 returns 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>
运行 它并查看安全错误:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading
the remote resource at http://www.reuters.com/. (Reason: CORS header
‘Access-Control-Allow-Origin’ missing).
Mozilla 对 Cross-site XMLHttpRequest 实现非常清楚:
Modern browsers support cross-site requests by implementing the Web
Applications (WebApps) Working Group's Access Control for Cross-Site
Requests standard.
As long as the server is configured to allow requests from your web
application's origin, XMLHttpRequest will work. Otherwise, an
INVALID_ACCESS_ERR exception is thrown.
我有一个单页应用程序(用户加载一堆 HTML/JS 然后发出 AJAX 请求而无需再次调用 MVC - 仅通过 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
都不为空。
客户端,我的所有 AJAX 请求都使用以下内容:
beforeSend: function (request) {
request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
},
使用 Razor 在我的 SPA 页面上生成一次令牌:
@Html.AntiForgeryToken()
我在 Web.config 中设置了我的机器密钥。
可能是什么原因造成的?
更新 我刚刚检查了日志,有时我也看到了这个:
The provided anti-forgery token was meant for user "", but the current user is "someuser@domain.com". a few seconds ago
当用户在登录时刷新他们的 SPA 实例时会发生这种情况。然后 SPA 出于某种原因将它们放入登录页面而不是内页(User.Identity.IsAuthenticated
为真)- 然后他们可以' 因为这个错误而登录。提神将它们拉回内部。不确定这意味着什么,但我认为更多信息不会造成伤害。
我试着给出同样的答案,如果在我们交换的评论中,你的情况似乎与我的不相关..
此类问题可能是由 XMLHttpRequest.setRequestHeader()
行为引起的,因为此函数“combines" the values of an header that has been already assigned in the context of an http request, as stated by MDN and Whatwg:
If this method is called several times with the same header, the values are merged into one single request header.
因此,如果我们有一个 SPA
例如执行所有 ajax POSTs
设置给定的 http header,在你的情况下:
beforeSend: function (request) {
request.setRequestHeader("X-XSRF-Token", $('input[name="__RequestVerificationToken"]').attr("value"););
}
第一个 ajax POST
请求设置了一个明确的 header ("X-XSRF-Token"
) 所以,服务器端,你应该有一个 "valid" header 要比较的值。
但是,如果没有页面刷新或新的 GET
请求,所有后续 ajax POSTs
,以及 MDN and Whatwg documentation, will make a dirty assignment of the same header ("X-XSRF-Token"
), because they 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"););
}
其他解决方案可以依赖于您必须自己实现的某些 client-side 状态处理机制,因为无法获取 http 请求的值或状态访问 headers (只能访问响应headers)。
更新 - 修订以下文字:
因此,如果我们有一个 SPA
例如执行所有 ajax POSTs
回收 XMLHttpRequest object 每次调用和设置给定的 http header,在你案件:
...
我的回答是建议不要尝试在 AJAX 调用中使用基于令牌的 CSRF 保护,而是依赖网络浏览器的原生 CORS 功能。
基本上,从浏览器到后端服务器的任何 AJAX 调用都会检查域来源(也就是从中加载脚本的域)。如果域匹配(JS 托管域 == 目标 AJAX 服务器域),AJAX 调用执行正常,否则 returns 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>
运行 它并查看安全错误:
Cross-Origin Request Blocked: The Same Origin Policy disallows reading the remote resource at http://www.reuters.com/. (Reason: CORS header ‘Access-Control-Allow-Origin’ missing).
Mozilla 对 Cross-site XMLHttpRequest 实现非常清楚:
Modern browsers support cross-site requests by implementing the Web Applications (WebApps) Working Group's Access Control for Cross-Site Requests standard.
As long as the server is configured to allow requests from your web application's origin, XMLHttpRequest will work. Otherwise, an INVALID_ACCESS_ERR exception is thrown.