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);
            }
        }
posted @ 2022-12-08 18:39  easy5  阅读(686)  评论(1编辑  收藏  举报