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.MvcAbpApplicationConfigurationController.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-TOKENanti-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)

實戰

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

配置類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 方法:

https://github.com/abpframework/abp/blob/48c52625f4c4df007f04d5ac6368b07411aa7521/framework/src/Volo.Abp.Swashbuckle/wwwroot/swagger/ui/abp.js

    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);
            }
        }
發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章