带有 SPA 架构的 ValidateAntiForgeryToken
Posted
技术标签:
【中文标题】带有 SPA 架构的 ValidateAntiForgeryToken【英文标题】:ValidateAntiForgeryToken with SPA architecture 【发布时间】:2013-03-03 05:09:14 【问题描述】:我正在尝试为 Hot Towel SPA 申请设置注册和登录。我基于asp.net single page application template 创建了 SimpleMembershipFilters 和 ValidateHttpAntiForgeryTokenAttribute。
你是怎么得到的
@html.AntiForgeryToken()
在 Durandal SPA 模式下工作的代码。
目前我有一个 register.html
<section>
<h2 data-bind="text: title"></h2>
<label>Firstname:</label><input data-bind="value: firstName" type="text" />
<label>Lastname:</label><input data-bind="value: lastName" type="text" />
<label>Email:</label><input data-bind="value: emailAddress" type="text" />
<label>Company:</label><input data-bind="value: company" type="text" />
<br />
<label>Password:</label><input data-bind="value: password1" type="password" />
<label>Re-Enter Password:</label><input data-bind="value: password2" type="password" />
<input type="button" value="Register" data-bind="click: registerUser" class="btn" />
</section>
register.js:
define(['services/logger'], function (logger)
var vm =
activate: activate,
title: 'Register',
firstName: ko.observable(),
lastName: ko.observable(),
emailAddress: ko.observable(),
company: ko.observable(),
password1: ko.observable(),
password2: ko.observable(),
registerUser: function ()
var d =
'FirstName': vm.firstName,
'LastName': vm.lastName,
'EmailAddress': vm.emailAddress,
'Company': vm.company,
'Password': vm.password1,
'ConfirmPassword': vm.password2
;
$.ajax(
url: 'Account/JsonRegister',
type: "POST",
data: d ,
success: function (result)
,
error: function (result)
);
,
;
return vm;
//#region Internal Methods
function activate()
logger.log('Login Screen Activated', null, 'login', true);
return true;
//#endregion
);
在 $ajax 调用中如何传递 AntiForgeryToken?另外我如何创建令牌?
【问题讨论】:
【参考方案1】:我会阅读this article,了解如何使用 javascript 使用防伪令牌。这篇文章是为 WebApi 编写的,但如果你愿意,它可以很容易地应用于 MVC 控制器。
简短的回答是这样的: 在您的 cshtml 视图中:
<script>
@functions
public string TokenHeaderValue()
string cookieToken, formToken;
AntiForgery.GetTokens(null, out cookieToken, out formToken);
return cookieToken + ":" + formToken;
$.ajax("api/values",
type: "post",
contentType: "application/json",
data: , // JSON data goes here
dataType: "json",
headers:
'RequestVerificationToken': '@TokenHeaderValue()'
);
</script>
然后在您的 asp.net 控制器中,您需要像这样验证令牌:
void ValidateRequestHeader(HttpRequestMessage request)
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
AntiForgery.Validate(cookieToken, formToken);
您希望在标头中传递它的原因是,如果您在请求的查询字符串或正文中的 ajax 调用中将其作为参数数据参数传递。然后,您将更难获得适用于所有不同场景的防伪令牌。因为您必须反序列化正文然后找到令牌。在标题中,它非常一致且易于检索。
**为光线编辑
这里是一个动作过滤器示例,您可以使用它来确定 Web api 方法的属性,以验证是否提供了防伪令牌。
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Helpers;
using System.Web.Http.Filters;
using System.Net.Http;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Threading;
namespace PAWS.Web.Classes.Filters
public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
if (actionContext == null)
throw new ArgumentNullException("HttpActionContext");
if (actionContext.Request.Method != HttpMethod.Get)
return ValidateAntiForgeryToken(actionContext, cancellationToken, continuation);
return continuation();
private Task<HttpResponseMessage> FromResult(HttpResponseMessage result)
var source = new TaskCompletionSource<HttpResponseMessage>();
source.SetResult(result);
return source.Task;
private Task<HttpResponseMessage> ValidateAntiForgeryToken(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
try
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("RequestVerificationToken", out tokenHeaders))
string[] tokens = tokenHeaders.First().Split(':');
if (tokens.Length == 2)
cookieToken = tokens[0].Trim();
formToken = tokens[1].Trim();
AntiForgery.Validate(cookieToken, formToken);
catch (System.Web.Mvc.HttpAntiForgeryException ex)
actionContext.Response = new HttpResponseMessage
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
;
return FromResult(actionContext.Response);
return continuation();
【讨论】:
你怎么称呼ValidateRequestHeader
?
我会创建一个动作过滤器,因为这是一个横切关注点并且是面向方面的设计。通过这种方式,您只需将要强制执行此安全性的方法归于属性。
仅供参考:该属性实现了IAuthorizationFilter
,但缺少public void OnAuthorization( AuthorizationContext filterContext )
方法。
这种方法的限制是:页面必须是 Razor 页面 .cshtml
并且我的 SPA 页面/视图是简单的 HTML 页面,因此它对我不起作用。
Jaider,这个概念应该适用于你想要的任何场景。是的,代码采用了这些假设,但概念保持不变。您在服务器上创建一个防伪令牌。根据请求将其传递给客户端。客户端必须在任何 PUT/POST/DELETE http 请求中使用此令牌,方法是将此令牌放在 http 标头中。这可以防止跨站点请求伪造。【参考方案2】:
在JS var中获取token的值
var antiForgeryToken = $('input[name="__RequestVerificationToken"]').val();
然后只需在 .ajax 调用的 beforeSend 函数中添加您的 ajax POST 标头
beforeSend: function (xhr, settings)
if (settings.data != "")
settings.data += '&';
settings.data += '__RequestVerificationToken=' + encodeURIComponent(antiForgeryToken);
【讨论】:
我必须添加到我的代码中的 _RequestVerfitcationToken 输入,但是如何以及在哪里可以设置输入的值?这是自动生成的东西吗?抱歉,这是新手。 是的,@Html.AntiForgeryToken() 会自动创建隐藏输入,这就是你在js中获取的值 因为我使用的是使用 Durandal 的 HotTowel 模板。 Durandal 遵循查找 .html 文件和 .js 文件的架构。 .html 文件是我们拥有所有 UI 元素的地方。 @ 调用在这里不可用,因为它是简单的 html 文件而不是 cshtml 文件。 默认可以直接使用html,但是在Durandal/Hot Towel下可以使用cshtml 啊,我们走了。我不知道。让我试试看。【参考方案3】:我对此有点挣扎,因为对于我基于热毛巾模板的 Durandal SPA 应用,现有答案似乎都不能正常工作。
我必须结合使用 Evan Larson 和 curtisk 的答案,才能得到我认为应该的方式。
在我的 index.cshtml 页面(Durandal 支持 cshtml 和 html)我在 </body>
标记上方添加了以下内容
@AntiForgery.GetHtml();
我按照 Evan Larson 的建议添加了一个自定义过滤器类,但是我必须对其进行修改以支持单独查找 cookie 值并使用 __RequestVerificationToken 作为名称而不是 RequestVerificationToken,因为这是由 AntiForgery.GetHtml() 提供的;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Helpers;
using System.Web.Http.Filters;
using System.Net.Http;
using System.Net;
using System.Threading.Tasks;
using System.Web.Http.Controllers;
using System.Threading;
using System.Net.Http.Headers;
namespace mySPA.Filters
public class ValidateJsonAntiForgeryTokenAttribute : FilterAttribute, IAuthorizationFilter
public Task<HttpResponseMessage> ExecuteAuthorizationFilterAsync(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
if (actionContext == null)
throw new ArgumentNullException("HttpActionContext");
if (actionContext.Request.Method != HttpMethod.Get)
return ValidateAntiForgeryToken(actionContext, cancellationToken, continuation);
return continuation();
private Task<HttpResponseMessage> FromResult(HttpResponseMessage result)
var source = new TaskCompletionSource<HttpResponseMessage>();
source.SetResult(result);
return source.Task;
private Task<HttpResponseMessage> ValidateAntiForgeryToken(HttpActionContext actionContext, CancellationToken cancellationToken, Func<Task<HttpResponseMessage>> continuation)
try
string cookieToken = "";
string formToken = "";
IEnumerable<string> tokenHeaders;
if (actionContext.Request.Headers.TryGetValues("__RequestVerificationToken", out tokenHeaders))
formToken = tokenHeaders.First();
IEnumerable<CookieHeaderValue> cookies = actionContext.Request.Headers.GetCookies("__RequestVerificationToken");
CookieHeaderValue tokenCookie = cookies.First();
if (tokenCookie != null)
cookieToken = tokenCookie.Cookies.First().Value;
AntiForgery.Validate(cookieToken, formToken);
catch (System.Web.Mvc.HttpAntiForgeryException ex)
actionContext.Response = new HttpResponseMessage
StatusCode = HttpStatusCode.Forbidden,
RequestMessage = actionContext.ControllerContext.Request
;
return FromResult(actionContext.Response);
return continuation();
随后在我的 App_Start/FilterConfig.cs 中添加了以下内容
public static void RegisterHttpFilters(HttpFilterCollection filters)
filters.Add(new ValidateJsonAntiForgeryTokenAttribute());
在我添加的 Global.asax 下的 Application_Start 中
FilterConfig.RegisterHttpFilters(GlobalConfiguration.Configuration.Filters);
最后,对于我的 ajax 调用,我添加了 curtisk 输入查找的派生,以便在我的 ajax 请求中添加标头,如果是登录请求。
var formForgeryToken = $('input[name="__RequestVerificationToken"]').val();
return Q.when($.ajax(
url: '/breeze/account/login',
type: 'POST',
contentType: 'application/json',
dataType: 'json',
data: JSON.stringify(data),
headers:
"__RequestVerificationToken": formForgeryToken
)).fail(handleError);
这导致我的所有发布请求都需要一个验证令牌,该验证令牌基于 AntiForgery.GetHtml() 创建的 cookie 和隐藏表单验证令牌;
据我了解,这将防止跨站点脚本攻击的可能性,因为攻击站点需要能够提供 cookie 和隐藏表单值才能验证自己,而这将更难获得.
【讨论】:
我需要修改以使其正常工作:将 AntiForgeryConfig.CookieName 中的 cookiename 设置为 __RequestVerificationToken(在我的系统上称为 __RequestVerificationToken_fugh485) 另外,tokenCookie.Cookies.First().Value;给了我错误的饼干。改为 tokenCookie.Cookies.First(c => c.Name == "__RequestVerificationToken").Value;【参考方案4】:如果使用 MVC 5,请阅读此解决方案!
我尝试了上述解决方案,但它们对我不起作用,从未达到动作过滤器,我不知道为什么。上面没有提到 MVC 版本,但我假设它是版本 4。我在我的项目中使用版本 5,并最终得到以下操作过滤器:
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Mvc;
using System.Web.Mvc.Filters;
namespace SydHeller.Filters
public class ValidateJSONAntiForgeryHeader : FilterAttribute, IAuthorizationFilter
public void OnAuthorization(AuthorizationContext filterContext)
string clientToken = filterContext.RequestContext.HttpContext.Request.Headers.Get(KEY_NAME);
if (clientToken == null)
throw new HttpAntiForgeryException(string.Format("Header does not contain 0", KEY_NAME));
string serverToken = filterContext.HttpContext.Request.Cookies.Get(KEY_NAME).Value;
if (serverToken == null)
throw new HttpAntiForgeryException(string.Format("Cookies does not contain 0", KEY_NAME));
System.Web.Helpers.AntiForgery.Validate(serverToken, clientToken);
private const string KEY_NAME = "__RequestVerificationToken";
-- 记下 using System.Web.Mvc
和 using System.Web.Mvc.Filters
,而不是 http
库(我认为这是 MVC v5 改变的事情之一。--
然后只需将过滤器[ValidateJSONAntiForgeryHeader]
应用于您的操作(或控制器),它应该会被正确调用。
在</body>
正上方的布局页面中,我有@AntiForgery.GetHtml();
最后,在我的 Razor 页面中,我执行 ajax 调用如下:
var formForgeryToken = $('input[name="__RequestVerificationToken"]').val();
$.ajax(
type: "POST",
url: serviceURL,
contentType: "application/json; charset=utf-8",
dataType: "json",
data: requestData,
headers:
"__RequestVerificationToken": formForgeryToken
,
success: crimeDataSuccessFunc,
error: crimeDataErrorFunc
);
【讨论】:
以上是关于带有 SPA 架构的 ValidateAntiForgeryToken的主要内容,如果未能解决你的问题,请参考以下文章