目前公司採用的開發框架是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的描述控制器的工作.
碰到奇葩問題,多看看官方源碼還是有好處的,有些實現並不是想當然的東西,還是需要實踐