十、Abp vNext 基础篇丨权限

介绍

本章节来把接口的权限加一下

权限配置和使用

官方地址:https://docs.abp.io/en/abp/latest/Authorization

下面这种代码可能我们日常开发都写过,ASP.NET Core 提供的Authorize特性来帮我们做授权,但是BookStore_Author_Create策略,需要我们去手动声明。

十、Abp vNext 基础篇丨权限

Abp定义了一个叫Permission System叫权限系统啥的都可以,来帮助我们轻松的搞定授权,具体怎么玩看下面代码就三部。

十、Abp vNext 基础篇丨权限

一、定义常量

    public static class CorePermissions
    {
        public const string GroupName = "Bvcp.Core";

        public static class Blogs
        {
            public const string Default = GroupName + ".Blog";
            public const string Management = Default + ".Management";
            public const string Delete = Default + ".Delete";
            public const string Update = Default + ".Update";
            public const string Create = Default + ".Create";
            public const string ClearCache = Default + ".ClearCache";
        }

        public static class Posts
        {
            public const string Default = GroupName + ".Post";
            public const string Delete = Default + ".Delete";
            public const string Update = Default + ".Update";
            public const string Create = Default + ".Create";
        }

        public static class Tags
        {
            public const string Default = GroupName + ".Tag";
            public const string Delete = Default + ".Delete";
            public const string Update = Default + ".Update";
            public const string Create = Default + ".Create";
        }

        public static class Comments
        {
            public const string Default = GroupName + ".Comment";
            public const string Delete = Default + ".Delete";
            public const string Update = Default + ".Update";
            public const string Create = Default + ".Create";
        }

        public static string[] GetAll()
        {
            return ReflectionHelper.GetPublicConstantsRecursively(typeof(CorePermissions));
        }
    }

二、添加权限配置

    public class CorePermissionDefinitionProvider : PermissionDefinitionProvider
    {
        public override void Define(IPermissionDefinitionContext context)
        {
            var bloggingGroup = context.AddGroup(CorePermissions.GroupName, L("Permission:Core"));

            var blogs = bloggingGroup.AddPermission(CorePermissions.Blogs.Default, L("Permission:Blogs"));
            blogs.AddChild(CorePermissions.Blogs.Management, L("Permission:Management"));
            blogs.AddChild(CorePermissions.Blogs.Update, L("Permission:Edit"));
            blogs.AddChild(CorePermissions.Blogs.Delete, L("Permission:Delete"));
            blogs.AddChild(CorePermissions.Blogs.Create, L("Permission:Create"));
            blogs.AddChild(CorePermissions.Blogs.ClearCache, L("Permission:ClearCache"));

            var posts = bloggingGroup.AddPermission(CorePermissions.Posts.Default, L("Permission:Posts"));
            posts.AddChild(CorePermissions.Posts.Update, L("Permission:Edit"));
            posts.AddChild(CorePermissions.Posts.Delete, L("Permission:Delete"));
            posts.AddChild(CorePermissions.Posts.Create, L("Permission:Create"));

            var tags = bloggingGroup.AddPermission(CorePermissions.Tags.Default, L("Permission:Tags"));
            tags.AddChild(CorePermissions.Tags.Update, L("Permission:Edit"));
            tags.AddChild(CorePermissions.Tags.Delete, L("Permission:Delete"));
            tags.AddChild(CorePermissions.Tags.Create, L("Permission:Create"));

            var comments = bloggingGroup.AddPermission(CorePermissions.Comments.Default, L("Permission:Comments"));
            comments.AddChild(CorePermissions.Comments.Update, L("Permission:Edit"));
            comments.AddChild(CorePermissions.Comments.Delete, L("Permission:Delete"));
            comments.AddChild(CorePermissions.Comments.Create, L("Permission:Create"));
        }

        private static LocalizableString L(string name)
        {
            return LocalizableString.Create<CoreResource>(name);
        }
    }

三、使用

Authorize(CorePermissions.Posts.Delete)]

十、Abp vNext 基础篇丨权限

资源授权方案

资源授权可能用过的人不多,代码都会在整体的修改代码一节这里先看就行,可以参考微软官方文档

https://docs.microsoft.com/zh-cn/aspnet/core/security/authorization/resourcebased?view=aspnetcore-5.0

有些场景下,授权需要依赖于要访问的资源,例如:每个资源通常会有一个创建者属性,我们只允许该资源的创建者才可以对其进行编辑,删除等操作,这就无法通过[Authorize]特性来指定授权了。因为授权过滤器会在我们的应用代码之前执行,无法确定所访问的资源。此时,我们需要使用基于资源的授权,下面就来演示一下具体是如何操作的。

定义资源Requirement

在基于资源的授权中,我们要判断的是用户是否具有针对该资源的某项操作,因此,我们先定义一个代表操作的Requirement:

    public static class CommonOperations
    {
        public static OperationAuthorizationRequirement Update = new OperationAuthorizationRequirement { Name = nameof(Update) };
        public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = nameof(Delete) };
    }

实现资源授权Handler

每一个 Requirement 都需要有一个对应的 Handler,来完成授权逻辑,可以直接让 Requirement 实现IAuthorizationHandler接口。

我们是根据资源的创建者来判断用户是否具有操作权限,实现我们的授权Handler:

  public class CommentAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Comment>
    {
        private readonly IPermissionChecker _permissionChecker;

        public CommentAuthorizationHandler(IPermissionChecker permissionChecker)
        {
            _permissionChecker = permissionChecker;
        }

        protected override async Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            OperationAuthorizationRequirement requirement,
            Comment resource)
        {
            if (requirement.Name == CommonOperations.Delete.Name && await HasDeletePermission(context, resource))
            {
                context.Succeed(requirement);
                return;
            }

            if (requirement.Name == CommonOperations.Update.Name && await HasUpdatePermission(context, resource))
            {
                context.Succeed(requirement);
                return;
            }
        }

        private async Task<bool> HasDeletePermission(AuthorizationHandlerContext context, Comment resource)
        {
            // 判断创建人是否是登陆人
            if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
            {
                return true;
            }
            // 判断当前用户是否满足资源操作策略
            if (await _permissionChecker.IsGrantedAsync(context.User, CorePermissions.Comments.Delete))
            {
                return true;
            }

            return false;
        }

        private async Task<bool> HasUpdatePermission(AuthorizationHandlerContext context, Comment resource)
        {
             // 判断创建人是否是登陆人
            if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
            {
                return true;
            }
            // 判断当前用户是否满足资源操作策略
            if (await _permissionChecker.IsGrantedAsync(context.User, CorePermissions.Comments.Update))
            {
                return true;
            }

            return false;
        }
    }

注册Handler,这一点不要忘了

 public class CoreApplicationModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            Configure<AbpAutoMapperOptions>(options =>
            {
                options.AddMaps<CoreApplicationModule>();
            });

            Configure<AuthorizationOptions>(options =>
            {
                options.AddPolicy("BloggingUpdatePolicy", policy => policy.Requirements.Add(CommonOperations.Update));
                options.AddPolicy("BloggingDeletePolicy", policy => policy.Requirements.Add(CommonOperations.Delete));
            });

            context.Services.AddSingleton<IAuthorizationHandler, CommentAuthorizationHandler>();
            context.Services.AddSingleton<IAuthorizationHandler, PostAuthorizationHandler>();
         

        }
    }

使用策略授权

        [Authorize]
        public async Task<CommentWithDetailsDto> UpdateAsync(Guid id, UpdateCommentDto input)
        {

            var comment = await _commentRepository.GetAsync(id);
            // 检测是否有权限
            await AuthorizationService.CheckAsync(comment, CommonOperations.Update);

            comment.SetText(input.Text);

            comment = await _commentRepository.UpdateAsync(comment);

            return ObjectMapper.Map<Comment, CommentWithDetailsDto>(comment);
        }

整体的修改代码

十、Abp vNext 基础篇丨权限

 public static class CommonOperations
    {
        public static OperationAuthorizationRequirement Update = new OperationAuthorizationRequirement { Name = nameof(Update) };
        public static OperationAuthorizationRequirement Delete = new OperationAuthorizationRequirement { Name = nameof(Delete) };
    }
 public class CommentAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Comment>
    {
        private readonly IPermissionChecker _permissionChecker;

        public CommentAuthorizationHandler(IPermissionChecker permissionChecker)
        {
            _permissionChecker = permissionChecker;
        }

        protected override async Task HandleRequirementAsync(
            AuthorizationHandlerContext context,
            OperationAuthorizationRequirement requirement,
            Comment resource)
        {
            if (requirement.Name == CommonOperations.Delete.Name && await HasDeletePermission(context, resource))
            {
                context.Succeed(requirement);
                return;
            }

            if (requirement.Name == CommonOperations.Update.Name && await HasUpdatePermission(context, resource))
            {
                context.Succeed(requirement);
                return;
            }
        }

        private async Task<bool> HasDeletePermission(AuthorizationHandlerContext context, Comment resource)
        {
            if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
            {
                return true;
            }

            if (await _permissionChecker.IsGrantedAsync(context.User, CorePermissions.Comments.Delete))
            {
                return true;
            }

            return false;
        }

        private async Task<bool> HasUpdatePermission(AuthorizationHandlerContext context, Comment resource)
        {
            if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
            {
                return true;
            }

            if (await _permissionChecker.IsGrantedAsync(context.User, CorePermissions.Comments.Update))
            {
                return true;
            }

            return false;
        }
    }
  public class PostAuthorizationHandler : AuthorizationHandler<OperationAuthorizationRequirement, Post>
    {
        private readonly IPermissionChecker _permissionChecker;

        public PostAuthorizationHandler(IPermissionChecker permissionChecker)
        {
            _permissionChecker = permissionChecker;
        }

        protected override async Task HandleRequirementAsync(AuthorizationHandlerContext context, OperationAuthorizationRequirement requirement,
            Post resource)
        {

            if (requirement.Name == CommonOperations.Delete.Name && await HasDeletePermission(context, resource))
            {
                context.Succeed(requirement);
                return;
            }

            if (requirement.Name == CommonOperations.Update.Name && await HasUpdatePermission(context, resource))
            {
                context.Succeed(requirement);
                return;
            }

        }

        private async Task<bool> HasDeletePermission(AuthorizationHandlerContext context, Post resource)
        {
            if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
            {
                return true;
            }

            if (await _permissionChecker.IsGrantedAsync(context.User, CorePermissions.Posts.Delete))
            {
                return true;
            }

            return false;
        }

        private async Task<bool> HasUpdatePermission(AuthorizationHandlerContext context, Post resource)
        {
            if (resource.CreatorId != null && resource.CreatorId == context.User.FindUserId())
            {
                return true;
            }

            if (await _permissionChecker.IsGrantedAsync(context.User, CorePermissions.Posts.Update))
            {
                return true;
            }

            return false;
        }
    }

CoreApplicationModule.cs

 public class CoreApplicationModule : AbpModule
    {
        public override void ConfigureServices(ServiceConfigurationContext context)
        {
            Configure<AbpAutoMapperOptions>(options =>
            {
                options.AddMaps<CoreApplicationModule>();
            });

            // 注册
            Configure<AuthorizationOptions>(options =>
            {
                options.AddPolicy("BloggingUpdatePolicy", policy => policy.Requirements.Add(CommonOperations.Update));
                options.AddPolicy("BloggingDeletePolicy", policy => policy.Requirements.Add(CommonOperations.Delete));
            });

            context.Services.AddSingleton<IAuthorizationHandler, CommentAuthorizationHandler>();
            context.Services.AddSingleton<IAuthorizationHandler, PostAuthorizationHandler>();
         

        }
    }

CommentAppService.cs

        [Authorize]
        public async Task<CommentWithDetailsDto> UpdateAsync(Guid id, UpdateCommentDto input)
        {

            var comment = await _commentRepository.GetAsync(id);

            await AuthorizationService.CheckAsync(comment, CommonOperations.Update);

            comment.SetText(input.Text);

            comment = await _commentRepository.UpdateAsync(comment);

            return ObjectMapper.Map<Comment, CommentWithDetailsDto>(comment);
        }

        [Authorize]
        public async Task DeleteAsync(Guid id)
        {
            var comment = await _commentRepository.GetAsync(id);

            await AuthorizationService.CheckAsync(comment, CommonOperations.Delete);

            var replies = await _commentRepository.GetRepliesOfComment(id);

            foreach (var reply in replies)
            {
                await _commentRepository.DeleteAsync(reply.Id);
            }
        }

PostAppService.cs

 [Authorize(CorePermissions.Posts.Delete)]
        public async Task DeleteAsync(Guid id)
        {
            // 查找文章
            var post = await _postRepository.GetAsync(id);
            // 判断是否有资源操作权
            await AuthorizationService.CheckAsync(post, CommonOperations.Delete);
            // 根据文章获取Tags
            var tags = await GetTagsOfPost(id);
            // 减少Tag引用数量
            await _tagRepository.DecreaseUsageCountOfTagsAsync(tags.Select(t => t.Id).ToList());
            // 删除评论
            await _commentRepository.DeleteOfPost(id);
            // 删除文章
            await _postRepository.DeleteAsync(id);
            await PublishPostChangedEventAsync(post.BlogId);
        }

        [Authorize(CorePermissions.Posts.Update)]
        public async Task<PostWithDetailsDto> UpdateAsync(Guid id, UpdatePostDto input)
        {
            var post = await _postRepository.GetAsync(id);

            input.Url = await RenameUrlIfItAlreadyExistAsync(input.BlogId, input.Url, post);

            await AuthorizationService.CheckAsync(post, CommonOperations.Update);

            post.SetTitle(input.Title);
            post.SetUrl(input.Url);
            post.Content = input.Content;
            post.Description = input.Description;
            post.CoverImage = input.CoverImage;

            post = await _postRepository.UpdateAsync(post);

            var tagList = SplitTags(input.Tags);
            await SaveTags(tagList, post);
            await PublishPostChangedEventAsync(post.BlogId);
            return ObjectMapper.Map<Post, PostWithDetailsDto>(post);
        }
上一篇:ABP vNext V5 + VS2022+ .Net 6.0 学习笔记(1)


下一篇:.NET Core之ABP vNext 开发实战(三) 熟悉项目构造