文檔
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);
}
}