介紹
本章節來把介面的許可權加一下
許可權配置和使用
官方地址:https://docs.abp.io/en/abp/latest/Authorization
下面這種程式碼可能我們日常開發都寫過,ASP.NET Core 提供的Authorize特性來幫我們做授權,但是BookStore_Author_Create
策略,需要我們去手動宣告。
Abp定義了一個叫Permission System
叫許可權系統啥的都可以,來幫助我們輕鬆的搞定授權,具體怎麼玩看下面程式碼就三部。
一、定義常量
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)]
資源授權方案
資源授權可能用過的人不多,程式碼都會在整體的修改程式碼
一節這裡先看就行,可以參考微軟官方文件
有些場景下,授權需要依賴於要訪問的資源,例如:每個資源通常會有一個建立者屬性,我們只允許該資源的建立者才可以對其進行編輯,刪除等操作,這就無法通過[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);
}
整體的修改程式碼
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);
}