knockout.js 使用SPA架构验证防伪令牌

zujrkrfu  于 2022-11-10  发布在  其他
关注(0)|答案(4)|浏览(139)

我正在尝试为热毛巾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>

注册表.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?以及我如何创建令牌?

2izufjch

2izufjch1#

我会阅读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 调用中把它作为parameter data参数传递,在请求的querystring或body中。那么你将很难在所有不同的场景中获得防伪令牌。因为你将不得不反序列化body,然后找到令牌。在头中,它非常一致,并且很容易检索。

**编辑射线

下面是一个操作过滤器的示例,您可以使用该过滤器来确定web api方法的属性,以验证是否提供了antiforgerytoken。

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();
        }
    }
}
v8wbuo2f

v8wbuo2f2#

获取JS变量中的标记值

var antiForgeryToken = $('input[name="__RequestVerificationToken"]').val();

然后,只需在**.ajax调用的beforeSend**函数中添加到 AJAX POST头

beforeSend: function (xhr, settings) {
            if (settings.data != "") {
                settings.data += '&';
            }
            settings.data += '__RequestVerificationToken=' +  encodeURIComponent(antiForgeryToken);
}
ctzwtxfj

ctzwtxfj3#

我有点纠结于此,因为现有的两个答案似乎都不适合我的基于热毛巾模板的Durandal SPA应用程序的情况。
我不得不使用埃文拉森和柯蒂斯克的答案的组合,以获得一些工作的方式,我认为它应该。
在我的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的input lookup的派生,以便在我的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和隐藏的表单值,以便能够验证自己,这将是更难获得的。

z2acfund

z2acfund4#

如果使用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.Mvcusing 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
});

相关问题