abp 以或的方式验证多个 AuthorizeAttribute

2021-09-26
前言

在使用 abp 开发业务功能时,会遇到公用同一个类的情况,在给这个类配置权限时,就要添加多个 AuthorizeAttribute,类似下面这样:

    [Authorize(DcsPermissions.DocCenter.Doc.Default)]
    [Authorize(DcsPermissions.WorkingPlatform.MyDraft.Default)]
    public class DocAppService : DcsAppServiceBase, IDocAppService
    {
        // ......
    }

但是 abp 在验证时,会以且的方式验证这两个 Policy,只要一个没有权限,则返回 403 状态码。如果想以或的方式(只要有一个有权限,那么就返回有权限)验证如何做呢?通过查看 abp 源码,我们可以新增一个 IMethodInvocationAuthorizationService 接口的实现替换掉 abp 默认的实现 MethodInvocationAuthorizationService 。这个类实现的唯一目的就是通过 AuthorizeAttribute 构造出 AuthorizationPolicy 然后使用 IAbpAuthorizationService 验证权限。下面看看我如何实现或的方式进行验证权限吧。

实现

代码不多,就直接看下面的代码吧

    [Dependency(ReplaceServices = true)]
    public class MyMethodInvocationAuthorizationService : IMethodInvocationAuthorizationService, ITransientDependency
    {
        private readonly IAbpAuthorizationService _abpAuthorizationService;

        public AutobioMethodInvocationAuthorizationService(IAbpAuthorizationService abpAuthorizationService)
        {
            this._abpAuthorizationService = abpAuthorizationService;
        }

        public async Task CheckAsync(MethodInvocationAuthorizationContext context)
        {
            if ( this.AllowAnonymous(context))
            {
                return;
            }

            var policyNames = this.GetAuthorizationDataPolicyNames(context.Method);
            if ( policyNames.Any() )
            {
                var isGranted = await this._abpAuthorizationService.IsGrantedAnyAsync(policyNames);
                if ( !isGranted )
                {
                    throw new AbpAuthorizationException(code: AbpAuthorizationErrorCodes.GivenPolicyHasNotGranted);
                }
            }
        }

        protected virtual bool AllowAnonymous(MethodInvocationAuthorizationContext context)
            => context.Method.GetCustomAttributes(true).OfType<IAllowAnonymous>().Any();

        protected virtual string[] GetAuthorizationDataPolicyNames(MethodInfo methodInfo)
        {
            var attributes = methodInfo
                .GetCustomAttributes(true)
                .OfType<IAuthorizeData>();

            if (methodInfo.IsPublic && methodInfo.DeclaringType != null)
            {
                attributes = attributes
                    .Union(
                        methodInfo.DeclaringType
                            .GetCustomAttributes(true)
                            .OfType<IAuthorizeData>()
                    );
            }

            return attributes.Where(_ => !string.IsNullOrWhiteSpace(_.Policy)).Select(_ => _.Policy).ToArray();
        }
    }

这里面主要利益于 abp 提供了 IsGrantedAnyAsync 扩展方法。

总结

打算把这个想法提个 PR 给 abp 项目。整个扩展过程下来的感觉 abp 挺灵活的。abp 默认达不到的要求,几乎都可以通过扩展它来解决掉。