ABP VNext添加全局认证(如何继承AuthorizeFilter)
目前公司采用的开发框架是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的描述控制器的工作.
碰到奇葩问题,多看看官方源码还是有好处的,有些实现并不是想当然的东西,还是需要实践