ABP VNext新增全域性認證(如何繼承AuthorizeFilter)

2023-07-25 15:02:14
前言

目前公司採用的開發框架是ABP VNext微服務架構

最近突然發現一個問題,ABP中如果控制器或服務層沒有加 Authorize特性的話,則不會走身份認證,且不會認證Token

如圖:

 

但是專案已開發大半,一個個去補Authorize特性,工作量比較大,也容易產生遺漏

就想著以前做單體應用的時候,有個全域性新增特性的方法,也就是如下程式碼:

Services.AddMvc(setupAction =>
{
   setupAction.Filters.Add<AuthorizeFilter>();
});

本以為這樣就萬事大吉了,沒想到還有坑在裡面..

我們都知道,ABP提供了服務間的動態API通訊功能,它的原理是先獲取對應服務的描述,然後通過描述來存取對應的服務節點,

也就是 api/abp/api-definition 這個描述JSON

我們用以上的程式碼新增了全域性授權之後會發現api-definition也被許可權管控了,由於api-definition是由ABP框架自動生成的,我們也無法在這個終結點上新增類似  AllowAnonymous 的過濾特性

 

正文

 

那麼應該如何解決這個問題呢?

首先想到的就是實現自己的授權特性,只需要繼承 IAsyncAuthorizationFilter,即可

但是如果採用自己的AuthorizationFilter,則需要重寫整個 OnAuthorizationAsync 事件.

ABP提供了角色之類的授權資訊就都需要自行重寫.

後來想到,可以繼承AuthorizeFilter ,新增我們想要的過濾之後直接執行父類別的方法,說幹就幹,我們繼承AuthorizeFilter ,程式碼實現如下:

    public class AbpAuthorizeFilter : AuthorizeFilter
    {

        public AbpAuthorizeFilter()
            : base()
        {
        }

        public override Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            //過濾動態API
            if (context.HttpContext.Request.Path.Value.EndsWith("/api-definition"))
            {
                return Task.CompletedTask;
            }
            return base.OnAuthorizationAsync(context);
        }

    }

 

可是當我們信心滿滿的把這個攔截器注入之後,會發現整個授權管道,壓根就不走自己的這個重寫方法.

找了很多資料,最終在官方的issues中找到了類似的疑問,Overrided OnAuthorizationAsync function from AuthorizeFilter can't work in customer class. · Issue #30025 · dotnet/aspnetcore (github.com)

是因為在.NET 5.0 之後,AuthorizeFilter繼承了 IFilterFactory,所以在生成範例的時候其實是來自於IFilterFactory的CreateInstance方法, 我們沒有重寫這個方法,所以一直產生的還是AuthorizeFilter 範例

我們修改程式碼如下:

    public class AbpAuthorizeFilter2 : AuthorizeFilter
    {

        public AbpAuthorizeFilter2()
            : base()
        {
        }

        public override Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            //過濾動態API
            if (context.HttpContext.Request.Path.Value.EndsWith("/api-definition"))
            {
                return Task.CompletedTask;
            }
            return base.OnAuthorizationAsync(context);
        }

        IFilterMetadata CreateInstance(IServiceProvider serviceProvider)
        {
            return this;
        }
    }

  

執行後發現,在執行到這個攔截器的時候,就會報錯,提示PolicyProvider 不能為空.  這就很納悶了,最終選擇去檢視一下AuthorizeFilter的原始碼,aspnetcore/src/Mvc/Mvc.Core/src/Authorization/AuthorizeFilter.cs at 1bda10b33b6cc6f3bbaceabbadb4ddd18ca6e68e · dotnet/aspnetcore (github.com)

我們發現他這個PolicyProvider物件來自於IOC容器,且在CreateInstance方法中判斷了這個類是否為空,如果為空則返回基礎類別自己,程式碼如下:

IFilterMetadata IFilterFactory.CreateInstance(IServiceProvider serviceProvider)
{
        if (Policy != null || PolicyProvider != null)
        {
            // The filter is fully constructed. Use the current instance to authorize.
            return this;
        }

        Debug.Assert(AuthorizeData != null);
        var policyProvider = serviceProvider.GetRequiredService<IAuthorizationPolicyProvider>();
        return AuthorizationApplicationModelProvider.GetFilter(policyProvider, AuthorizeData);
 }

 

那我們就好辦了,直接從IOC容器中拿到IAuthorizationPolicyProvider這個實現類,提供給基礎類別即可,我們修改程式碼如下:

    public class AbpAuthorizeFilter:AuthorizeFilter 
    {

        public AbpAuthorizeFilter(IServiceProvider serviceProvider)
            : base(policyProvider: serviceProvider.GetRequiredService<IAuthorizationPolicyProvider>(), authorizeData: new[] { new AuthorizeAttribute() })
        {
        }
        public void OnAuthorization(AuthorizationFilterContext context)
        {
            OnAuthorizationAsync(context);
        }

        public override Task OnAuthorizationAsync(AuthorizationFilterContext context)
        {
            //過濾動態API
            if (context.HttpContext.Request.Path.Value.EndsWith("/api-definition"))
            {
                return Task.CompletedTask;
            }
            return base.OnAuthorizationAsync(context);
        }

    }

然後修改HostModule中全域性授權的方法如下(.NETCORE 是Startup)

context.Services.AddMvc(setupAction =>
{
  //新增自定義的全域性攔截器
  setupAction.Filters.Add<AbpAuthorizeFilter>();

});

 

至此,我們就完成了過濾abp的描述控制器的工作.

後記

碰到奇葩問題,多看看官方原始碼還是有好處的,有些實現並不是想當然的東西,還是需要實踐