【问题标题】:ValidateAntiForgeryToken in Ajax request with AspNet Core MVC使用 AspNet Core MVC 验证 Ajax 请求中的 ValidateAntiForgeryToken
【发布时间】:2016-08-06 08:03:24
【问题描述】:

我一直在尝试重新创建 ValidateAntiForgeryToken 的 Ajax 版本 - 有很多关于如何为以前版本的 MVC 执行此操作的博客文章,但对于最新的 MVC 6,没有任何代码是相关的。不过,我要遵循的核心原则是让验证查看__RequestVerificationToken 的 Cookie 和 Header,而不是将 Cookie 与表单值进行比较。我使用的是 MVC 6.0.0-rc1-final、dnx451 框架,所有 Microsoft.Extensions 库都是 1.0.0-rc1-final。

我最初的想法是只继承ValidateAntiForgeryTokenAttribute,但是查看源代码,我需要返回我自己的授权过滤器实现以使其查看标头。

[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&lt;T&gt;() 的通用方法,所以在我停滞不前之前我已经做到了这一点。 DefaultAntiforgery 的源代码有 Microsoft.Extensions.Options,但我在任何 Nuget 包中都找不到该命名空间。 Microsoft.Extensions.OptionsModel 存在,但只是引入了IOptions&lt;out TOptions&gt; 接口。

为了跟进所有这些,一旦我让授权过滤器工作,或者我获得了IAntiforgery 的新实现,我在哪里或如何将它注册到依赖注入以使用它 - 并且仅用于我将接受 Ajax 请求的操作?

【问题讨论】:

    标签: c# ajax asp.net-core-mvc dnx antiforgerytoken


    【解决方案1】:

    我有类似的问题。我不知道 .NET 中是否会对此进行任何更改,但当时,我在 Startup.cs 中的 ConfigureServices 方法中添加了以下几行,然后行 services.AddMvc(),以验证通过 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 页面 - 完美运行
    【解决方案2】:

    我一直在努力解决类似的情况,将 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() 方法中使用以下行将其插入到管道中:

    app.UseAngularXSRF();
    

    我在调用 app.UseMVC() 之前就这样做了。

    请注意,此扩展在其存在的任何 POST、PUT 或 DELETE 上传输 XSRF 标头,并且它通过覆盖现有的表单字段集合来实现。这符合我的设计模式——XSRF 标头出现在请求中的唯一情况是它来自我编写的一些角度代码——但它可能不适合你的。

    我还认为您需要配置防伪子系统以使用正确的名称作为 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] 仅适用于类实例。尽管在我的例子中我只对单个整数感兴趣,但我仍然必须模拟一个仅包含单个整数属性的类。

    希望这会有所帮助。

    【讨论】:

      【解决方案3】:

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

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

      改为使用访问令牌实现,将允许您刷新访问令牌(使用刷新令牌),进行长时间运行的会话,并阻止 cookie 窃贼访问您的 API。它还将阻止 XSRF :)

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

      【讨论】:

        猜你喜欢
        • 2018-07-17
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 1970-01-01
        • 2018-01-24
        • 2019-08-03
        • 2020-08-20
        • 2014-02-22
        相关资源
        最近更新 更多