在使用AspNet Core MVC时,在Ajax请求中验证AntiForgeryToken

13

我一直在尝试重新创建一个基于Ajax的ValidateAntiForgeryToken版本 - 有很多博客文章介绍如何为以前的MVC版本实现这一点,但是对于最新的MVC 6,没有任何代码是相关的。然而,我追求的核心原则是让验证程序查看Cookie和Header中的__RequestVerificationToken,而不是将Cookie与表单值进行比较。我正在使用MVC 6.0.0-rc1-final、dnx451框架,并且所有Microsoft.Extensions库都是1.0.0-rc1-final。

我的初始想法是继承ValidateAntiForgeryTokenAttribute,但是查看源代码后,我需要返回自己实现的授权过滤器来让它查看Header。

[AttributeUsage(AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class ValidateAjaxAntiForgeryTokenAttribute : Attribute, IFilterFactory, IFilterMetadata, IOrderedFilter
{
    public int Order { get; set; }
    public bool IsReusable => true;
    public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
    {
        return serviceProvider.GetRequiredService<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
    }
}

因此,我制作了自己的版本:ValidateAntiforgeryTokenAuthorizationFilter

public class ValidateAjaxAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy
{
    private readonly IAntiforgery _antiforgery;
    private readonly ILogger _logger;
    public ValidateAjaxAntiforgeryTokenAuthorizationFilter(IAntiforgery antiforgery, ILoggerFactory loggerFactory)
    {
        if (antiforgery == null)
        {
            throw new ArgumentNullException(nameof(antiforgery));
        }
        _antiforgery = antiforgery;
        _logger = loggerFactory.CreateLogger<ValidateAjaxAntiforgeryTokenAuthorizationFilter>();
    }
    public async Task OnAuthorizationAsync(AuthorizationContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
        if (IsClosestAntiforgeryPolicy(context.Filters) && ShouldValidate(context))
        {
            try
            {
                await _antiforgery.ValidateRequestAsync(context.HttpContext);
            }
            catch (AjaxAntiforgeryValidationException exception)
            {
                _logger.LogInformation(1, string.Concat("Ajax Antiforgery token validation failed. ", exception.Message));
                context.Result = new BadRequestResult();
            }
        }
    }
    protected virtual bool ShouldValidate(AuthorizationContext context)
    {
        if (context == null)
        {
            throw new ArgumentNullException(nameof(context));
        }
        return true;
    }
    private bool IsClosestAntiforgeryPolicy(IList<IFilterMetadata> filters)
    {
        // Determine if this instance is the 'effective' antiforgery policy.
        for (var i = filters.Count - 1; i >= 0; i--)
        {
            var filter = filters[i];
            if (filter is IAntiforgeryPolicy)
            {
                return object.ReferenceEquals(this, filter);
            }
        }
        Debug.Fail("The current instance should be in the list of filters.");
        return false;
    }
}

然而,我找不到包含IAntiforgeryPolicy的正确Nuget包和命名空间。虽然我在GitHub上找到了接口,但我应该在哪个包中找到它呢?

我的下一个尝试是去寻找IAntiforgery注入,并将DefaultAntiforgery替换为我的AjaxAntiforgery

public class AjaxAntiforgery : DefaultAntiforgery
{
    private readonly AntiforgeryOptions _options;
    private readonly IAntiforgeryTokenGenerator _tokenGenerator;
    private readonly IAntiforgeryTokenSerializer _tokenSerializer;
    private readonly IAntiforgeryTokenStore _tokenStore;
    private readonly ILogger<AjaxAntiforgery> _logger;
    public AjaxAntiforgery(
        IOptions<AntiforgeryOptions> antiforgeryOptionsAccessor,
        IAntiforgeryTokenGenerator tokenGenerator,
        IAntiforgeryTokenSerializer tokenSerializer,
        IAntiforgeryTokenStore tokenStore,
        ILoggerFactory loggerFactory)
    {
        _options = antiforgeryOptionsAccessor.Value;
        _tokenGenerator = tokenGenerator;
        _tokenSerializer = tokenSerializer;
        _tokenStore = tokenStore;
        _logger = loggerFactory.CreateLogger<AjaxAntiforgery>();
    }
}

由于ILoggerFactory上没有通用方法CreateLogger<T>(),我在这里被卡住了。 DefaultAntiforgery的源代码有Microsoft.Extensions.Options,但我找不到任何Nuget包中有该命名空间。存在Microsoft.Extensions.OptionsModel,但这只引入了IOptions<out TOptions>接口。

至于后续步骤,一旦我使授权过滤器正常工作,或获得一个新的IAntiforgery实现,我应该在何处或如何将其注册到依赖注入以仅用于我将接受Ajax请求的操作?

3个回答

12

我曾经遇到过类似的问题。我不知道在 .NET 中是否会有任何关于此的更改,但是当时我在 Startup.csConfigureServices 方法中添加了以下行,在服务添加 Mvc() 之前,以验证通过 Ajax 发送的 AntiForgeryToken:

services.AddAntiforgery(options =>
{
    options.CookieName = "yourChosenCookieName"; 
    options.HeaderName = "RequestVerificationToken";
});

AJAX调用可能如下所示:

var token = $('input[type=hidden][name=__RequestVerificationToken]', document).val();

var request = $.ajax({
    data: { 'yourField': 'yourValue' },
    ...
    headers: { 'RequestVerificationToken': token }
});   

然后,在您的操作中使用本地属性[ValidadeAntiForgeryToken]即可。


谢谢。使用 .Net Core 2 Razor 页面 - 运行完美。 - Gfw

4

我一直在处理类似的情况,将Angular的POST与MVC6进行接口对接,并想出了以下解决方案。

需要解决两个问题:将安全令牌传递到MVC的反伪造验证子系统中,并将Angular的JSON格式回发数据转换为MVC模型。

我通过在Startup.Configure()中插入自定义中间件来处理第一步。该中间件类非常简单:

public static class UseAngularXSRFExtension
{
    public const string XSRFFieldName = "X-XSRF-TOKEN";

    public static IApplicationBuilder UseAngularXSRF( this IApplicationBuilder builder )
    {
        return builder.Use( next => context =>
        {
            switch( context.Request.Method.ToLower() )
            {
                case "post":
                case "put":
                case "delete":
                    if( context.Request.Headers.ContainsKey( XSRFFieldName ) )
                    {
                        var formFields = new Dictionary<string, StringValues>()
                        {
                            { XSRFFieldName, context.Request.Headers[XSRFFieldName] }
                        };

                        // this assumes that any POST, PUT or DELETE having a header
                        // which includes XSRFFieldName is coming from angular, so 
                        // overwriting context.Request.Form is okay (since it's not
                        // being parsed by MVC's internals anyway)
                        context.Request.Form = new FormCollection( formFields );
                    }

                    break;
            }

            return next( context );
        } );
    }
}

你可以在Startup.Configure()方法中添加以下代码将其插入到流水线中:

你需要在Startup.Configure()方法内部使用以下行:

app.UseAngularXSRF();

在调用app.UseMVC()之前,我执行了这个操作。

注意,这个扩展在任何存在 XSRF header 的 POST、PUT 或 DELETE 请求中都会转移它,并通过覆盖现有的表单字段集合来实现。这符合我的设计模式 - 只有当请求来自我编写的一些 Angular 代码时,XSRF header 才会出现在请求中 - 但可能不适合你的设计模式。

我还认为你需要配置防伪系统以使用正确的 XSRF 字段名称(我不确定默认值是什么)。你可以通过将以下行插入到 Startup.ConfigureServices() 中来完成此操作:

    services.ConfigureAntiforgery( options => options.FormFieldName = UseAngularXSRFExtension.XSRFFieldName );

在services.AddAntiforgery()这行代码前插入以下代码:

有几种方式将XSRF令牌添加到请求流中。我通常在视图中添加以下内容:

...top of view...
@inject Microsoft.AspNet.Antiforgery.IAntiforgery af
...rest of view...

...inside the angular function...
            var postHeaders = {
                'X-XSRF-TOKEN': '@(af.GetTokens(this.Context).FormToken)',
                'Content-Type': 'application/json; charset=utf-8',
            };

            $http.post( '/Dataset/DeleteDataset', JSON.stringify({ 'siteID': siteID }),
                {
                    headers: postHeaders,
                })
...rest of view...

第二部分——将JSON数据翻译成对象——需要在您的操作方法中使用[FromBody]修饰模型类来处理:
        // the [FromBody] attribute on the model -- and a class model, rather than a
        // single integer model -- are necessary so that MVC can parse the JSON-formatted
        // text POSTed by angular
        [HttpPost]
        [ValidateAntiForgeryToken]
        public IActionResult DeleteDataset( [FromBody] DeleteSiteViewModel model )
        {
}

[FromBody] 只能用在类实例上。虽然在我的情况下我只关心一个整数,但我仍然需要创建一个类,该类仅包含一个整数属性。

希望这可以帮到你。


0

在 Ajax 调用中使用防伪标记是可能的,但如果您正在尝试保护 Api,我真的建议使用访问令牌。

如果您依赖存储在 cookie 中的身份验证令牌作为 Api 的身份验证方式,则需要编写代码来补偿当 cookie 身份验证超时时,您的 Ajax 帖子被重定向到登录屏幕。这对于 SPAs 和 Angular 应用程序尤其重要。

相反,使用访问令牌实现将允许您刷新访问令牌(使用刷新令牌),以拥有长时间运行的会话,并阻止 cookie 盗贼访问您的 Apis... 这也将停止 XSRF :)

访问令牌的目的是保护资源,如 Web Apis。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接