我一直在尝试重新创建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<T>()
上没有通用方法。 DefaultAntiforgery
的源代码有Microsoft.Extensions.Options
,但我无法在任何Nuget包中找到该命名空间。 Microsoft.Extensions.OptionsModel
存在,但只会引入IOptions<out TOptions>
界面。
要完成所有这些操作,一旦我让授权过滤器工作,或者我得到IAntiforgery
的新实现,我在哪里或如何使用依赖注入来注册它以使用它 - 并且只有对于我将接受Ajax请求的操作?
答案 0 :(得分:11)
我有类似的问题。我不知道在.NET中是否会发生任何变化,但当时我在 Startup.cs 中添加了以下行到 ConfigureServices 方法, line 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] 。
答案 1 :(得分:3)
我一直在与类似的情况搏斗,将角度POST与MVC6接口,并提出以下建议。
需要解决两个问题:将安全令牌放入MVC的防伪验证子系统,并将角度的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 );
我在行service.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]仅适用于类实例。即使在我的情况下,我感兴趣的只是一个整数,我仍然需要假设一个类,它只包含一个整数属性。
希望这有帮助。
答案 2 :(得分:0)
在Ajax调用中使用反伪造令牌是可能的,但如果你想要确保Api,我真的建议使用访问令牌。
如果您依赖存储在Cookie中的身份令牌作为Api的身份验证,则需要编写代码以补偿Cookie身份验证超时,并且您的Ajax帖子被重定向到登录屏幕。这对于SPA和Angular应用程序尤其重要。
使用访问令牌实现,将允许您刷新访问令牌(使用刷新令牌),长时间运行会话并阻止cookie窃贼访问您的Apis ..它也将停止XSRF:)< / p>
访问令牌的目的是保护资源,例如Web Apis。