Abp:CSRF Anti Forgery
文档
https://docs.abp.io/en/abp/latest/CSRF-Anti-Forgery
https://learn.microsoft.com/en-us/aspnet/core/security/anti-request-forgery?view=aspnetcore-7.0
CSRF Anti Forgery token
什么时候写入Cookie?
在调用 /api/abp/application-configuration
时写入。
源代码:位于包:Volo.Abp.AspNetCore.Mvc 的 AbpApplicationConfigurationController.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[Area("abp")]
[RemoteService(Name = "abp")]
[Route("api/abp/application-configuration")]
public class AbpApplicationConfigurationController : AbpControllerBase, IAbpApplicationConfigurationAppService
{
private readonly IAbpApplicationConfigurationAppService _applicationConfigurationAppService;
private readonly IAbpAntiForgeryManager _antiForgeryManager;
public AbpApplicationConfigurationController(
IAbpApplicationConfigurationAppService applicationConfigurationAppService,
IAbpAntiForgeryManager antiForgeryManager)
{
_applicationConfigurationAppService = applicationConfigurationAppService;
_antiForgeryManager = antiForgeryManager;
}
[HttpGet]
public virtual async Task<ApplicationConfigurationDto> GetAsync(
ApplicationConfigurationRequestOptions options)
{
_antiForgeryManager.SetCookie(); //这里生成 antiForgery token 并写入Cookie,默认Cookie名为:XSRF-TOKEN
return await _applicationConfigurationAppService.GetAsync(options);
}
}
调用 /api/abp/application-configuration
时,如下图所示:
在Cookie中获取 anti-forgery token
在Cookie中获取名为:XSRF-TOKEN 的 anti-forgery token
查看 cookie,在浏览器的控制台输入
document.cookie
或者
abp.utils.getCookieValue = function (key) {
var equalities = document.cookie.split('; ');
for (var i = 0; i < equalities.length; i++) {
if (!equalities[i]) {
continue;
}
var splitted = equalities[i].split('=');
if (splitted.length != 2) {
continue;
}
if (decodeURIComponent(splitted[0]) === key) {
return decodeURIComponent(splitted[1] || '');
}
}
return null;
};
abp.utils.getCookieValue('XSRF-TOKEN');
设置请求头 RequestVerificationToken
之后,在Swagger的每个请求,Abp框架都会自动在http的请求头header 中添加:anti-forgery token
"RequestVerificationToken":(anti-forgery token)
实战
如何生成 anti-forgery token 并写入 Cookie
Abp 框架已经完成了这部分工作,
源代码:包 Volo.Abp.AspNetCore.Mvc 的 AbpApplicationConfigurationController.cs
using System.Threading.Tasks;
using Microsoft.AspNetCore.Mvc;
using Volo.Abp.AspNetCore.Mvc.AntiForgery;
namespace Volo.Abp.AspNetCore.Mvc.ApplicationConfigurations;
[Area("abp")]
[RemoteService(Name = "abp")]
[Route("api/abp/application-configuration")]
public class AbpApplicationConfigurationController : AbpControllerBase, IAbpApplicationConfigurationAppService
{
private readonly IAbpApplicationConfigurationAppService _applicationConfigurationAppService;
private readonly IAbpAntiForgeryManager _antiForgeryManager;
public AbpApplicationConfigurationController(
IAbpApplicationConfigurationAppService applicationConfigurationAppService,
IAbpAntiForgeryManager antiForgeryManager)
{
_applicationConfigurationAppService = applicationConfigurationAppService;
_antiForgeryManager = antiForgeryManager;
}
[HttpGet]
public virtual async Task<ApplicationConfigurationDto> GetAsync(
ApplicationConfigurationRequestOptions options)
{
_antiForgeryManager.SetCookie(); //这里生成 antiForgery token 并写入Cookie,默认Cookie名为:XSRF-TOKEN
return await _applicationConfigurationAppService.GetAsync(options);
}
}
其内部是使用 IAbpAntiForgeryManager
生成 anti-forgery token
并将其写入 Cookie
,参见:
AspNetCoreAbpAntiForgeryManager : IAbpAntiForgeryManager
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Http;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery;
public class AspNetCoreAbpAntiForgeryManager : IAbpAntiForgeryManager, ITransientDependency
{
protected AbpAntiForgeryOptions Options { get; }
protected HttpContext HttpContext => _httpContextAccessor.HttpContext;
private readonly IAntiforgery _antiforgery;
private readonly IHttpContextAccessor _httpContextAccessor;
public AspNetCoreAbpAntiForgeryManager(
IAntiforgery antiforgery,
IHttpContextAccessor httpContextAccessor,
IOptions<AbpAntiForgeryOptions> options)
{
_antiforgery = antiforgery;
_httpContextAccessor = httpContextAccessor;
Options = options.Value;
}
public virtual void SetCookie()
{
// 设定 antiforgery token
HttpContext.Response.Cookies.Append(
Options.TokenCookie.Name,
GenerateToken(),
Options.TokenCookie.Build(HttpContext)
);
}
public virtual string GenerateToken()
{
return _antiforgery.GetAndStoreTokens(_httpContextAccessor.HttpContext).RequestToken;
}
}
AbpAntiForgeryOptions
public AbpAntiForgeryOptions()
{
AutoValidateFilter = type => true;
TokenCookie = new CookieBuilder
{
Name = "XSRF-TOKEN",
HttpOnly = false,
IsEssential = true,
SameSite = SameSiteMode.None,
Expiration = TimeSpan.FromDays(3650) //10 years!
};
AuthCookieSchemaName = "Identity.Application";
AutoValidateIgnoredHttpMethods = new HashSet<string> { "GET", "HEAD", "TRACE", "OPTIONS" };
}
全局设定 anti-forgery token 验证
源代码:包 Volo.Abp.AspNetCore.Mvc,
代码清单:abp/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AbpAspNetCoreMvcModule.cs
...
namespace Volo.Abp.AspNetCore.Mvc;
public class AbpAspNetCoreMvcModule : AbpModule
{
public override void ConfigureServices(ServiceConfigurationContext context)
{
...
var mvcCoreBuilder = context.Services.AddMvcCore(options =>
{
options.Filters.Add(new AbpAutoValidateAntiforgeryTokenAttribute());
});
context.Services.ExecutePreConfiguredActions(mvcCoreBuilder);
...
}
}
这时使用AddMvcCore(...),即为 Mvc, Web Api 框架的Controller
加上特性:AbpAutoValidateAntiforgeryTokenAttribute
代码清单:AbpAutoValidateAntiforgeryTokenAttribute.cs
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery;
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, AllowMultiple = false, Inherited = true)]
public class AbpAutoValidateAntiforgeryTokenAttribute : Attribute, IFilterFactory, IOrderedFilter
{
public int Order { get; set; } = 1000;
public bool IsReusable => true;
public IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
{
return serviceProvider.GetRequiredService<AbpAutoValidateAntiforgeryTokenAuthorizationFilter>();
}
}
这样,对添加了特性AbpAutoValidateAntiforgeryTokenAttribute
使用如下选择器:AbpAutoValidateAntiforgeryTokenAuthorizationFilter
进行拦截
代码清单:abp/framework/src/Volo.Abp.AspNetCore.Mvc/Volo/Abp/AspNetCore/Mvc/AntiForgery/AbpAutoValidateAntiforgeryTokenAuthorizationFilter.cs
using System;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc.Abstractions;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery;
public class AbpAutoValidateAntiforgeryTokenAuthorizationFilter : AbpValidateAntiforgeryTokenAuthorizationFilter, ITransientDependency
{
private readonly AbpAntiForgeryOptions _options;
public AbpAutoValidateAntiforgeryTokenAuthorizationFilter(
IAntiforgery antiforgery,
AbpAntiForgeryCookieNameProvider antiForgeryCookieNameProvider,
IOptions<AbpAntiForgeryOptions> options,
ILogger<AbpValidateAntiforgeryTokenAuthorizationFilter> logger)
: base(
antiforgery,
antiForgeryCookieNameProvider,
logger)
{
_options = options.Value;
}
protected override bool ShouldValidate(AuthorizationFilterContext context)
{
if (!_options.AutoValidate)
{
return false;
}
if (context.ActionDescriptor.IsControllerAction())
{
var controllerType = context.ActionDescriptor
.AsControllerActionDescriptor()
.ControllerTypeInfo
.AsType();
if (!_options.AutoValidateFilter(controllerType))
{
return false;
}
}
if (IsIgnoredHttpMethod(context))
{
return false;
}
return base.ShouldValidate(context);
}
protected virtual bool IsIgnoredHttpMethod(AuthorizationFilterContext context)
{
return context.HttpContext
.Request
.Method
.ToUpperInvariant()
.IsIn(_options.AutoValidateIgnoredHttpMethods);
}
}
而AbpAutoValidateAntiforgeryTokenAuthorizationFilter
又继承自:AbpValidateAntiforgeryTokenAuthorizationFilter
,其实就是个IAsyncAuthorizationFilter
选择器,如下代码所示:
代码清单:
using System;
using System.Threading.Tasks;
using Microsoft.AspNetCore.Antiforgery;
using Microsoft.AspNetCore.Mvc;
using Microsoft.AspNetCore.Mvc.Filters;
using Microsoft.AspNetCore.Mvc.ViewFeatures;
using Microsoft.Extensions.Logging;
using Volo.Abp.DependencyInjection;
namespace Volo.Abp.AspNetCore.Mvc.AntiForgery;
public class AbpValidateAntiforgeryTokenAuthorizationFilter : IAsyncAuthorizationFilter, IAntiforgeryPolicy, ITransientDependency
{
private IAntiforgery _antiforgery;
private readonly AbpAntiForgeryCookieNameProvider _antiForgeryCookieNameProvider;
private readonly ILogger<AbpValidateAntiforgeryTokenAuthorizationFilter> _logger;
public AbpValidateAntiforgeryTokenAuthorizationFilter(
IAntiforgery antiforgery,
AbpAntiForgeryCookieNameProvider antiForgeryCookieNameProvider,
ILogger<AbpValidateAntiforgeryTokenAuthorizationFilter> logger)
{
_antiforgery = antiforgery;
_logger = logger;
_antiForgeryCookieNameProvider = antiForgeryCookieNameProvider;
}
public async Task OnAuthorizationAsync(AuthorizationFilterContext context)
{
if (context == null)
{
throw new ArgumentNullException(nameof(context));
}
if (!context.IsEffectivePolicy<IAntiforgeryPolicy>(this))
{
_logger.LogInformation("Skipping the execution of current filter as its not the most effective filter implementing the policy " + typeof(IAntiforgeryPolicy));
return;
}
if (ShouldValidate(context))
{
try
{
await _antiforgery.ValidateRequestAsync(context.HttpContext);
}
catch (AntiforgeryValidationException exception)
{
_logger.LogWarning(exception.Message, exception);
context.Result = new AntiforgeryValidationFailedResult();
}
}
}
protected virtual bool ShouldValidate(AuthorizationFilterContext context)
{
var authCookieName = _antiForgeryCookieNameProvider.GetAuthCookieNameOrNull();
//Always perform antiforgery validation when request contains authentication cookie
if (authCookieName != null &&
context.HttpContext.Request.Cookies.ContainsKey(authCookieName))
{
return true;
}
var antiForgeryCookieName = _antiForgeryCookieNameProvider.GetAntiForgeryCookieNameOrNull();
//No need to validate if antiforgery cookie is not sent.
//That means the request is sent from a non-browser client.
//See https://github.com/aspnet/Antiforgery/issues/115
if (antiForgeryCookieName != null &&
!context.HttpContext.Request.Cookies.ContainsKey(antiForgeryCookieName))
{
return false;
}
// Anything else requires a token.
return true;
}
}
前端从Cookie中获取 anti-forgery token:
abp.security.antiForgery.tokenCookieName = 'XSRF-TOKEN';
abp.security.antiForgery.getToken = function () {
return abp.utils.getCookieValue(abp.security.antiForgery.tokenCookieName);
};
其中,
XSRF-TOKEN
Cookie名为什么是 XSRF-TOKEN,参见类AbpAntiForgeryOptions
....
public AbpAntiForgeryOptions()
{
...
TokenCookie = new CookieBuilder
{
Name = "XSRF-TOKEN",
...
};
}
故在js中这样设置:
abp.security.antiForgery.tokenCookieName = 'XSRF-TOKEN';
getCookieValue 方法:
abp.utils.getCookieValue = function (key) {
var equalities = document.cookie.split('; ');
for (var i = 0; i < equalities.length; i++) {
if (!equalities[i]) {
continue;
}
var splitted = equalities[i].split('=');
if (splitted.length != 2) {
continue;
}
if (decodeURIComponent(splitted[0]) === key) {
return decodeURIComponent(splitted[1] || '');
}
}
return null;
};
ajax请求添加header
:RequestVerificationToken
为 ajax请求添加header
,名称必须是:RequestVerificationToken:
为什么是名称必须是:RequestVerificationToken,见:Prevent Cross-Site Request Forgery (XSRF/CSRF) attacks in ASP.NET Core
abp.security.antiForgery.tokenHeaderName = 'RequestVerificationToken';
...
ajaxSendHandler: function (event, request, settings) {
var token = abp.security.antiForgery.getToken();
if (!token) {
return;
}
if (!settings.headers || settings.headers[abp.security.antiForgery.tokenHeaderName] === undefined) {
request.setRequestHeader(abp.security.antiForgery.tokenHeaderName, token);
}
}