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