造轮子之权限管理
上文已经完成了自定义授权策略,那么接下来就得完善我们的权限管理了。不然没有数据,如何鉴权~
表设计
创建我们的表实体类:
namespace Wheel.Domain.Permissions
{
public class PermissionGrant : Entity<Guid>
{
public string Permission { get; set; }
public string GrantType { get; set; }
public string GrantValue { get; set; }
}
}
Permission表示权限名称,结构为"{controllerName}:{actionName}"。
GrantType表示权限类型,如角色权限则R表示,方便后续在新增别的权限类型时可以灵活扩展。
GrantValue则表示权限类型对应的值,比如GrantType是R时,GrantValue是admin则表示admin角色的授权。
修改DbContext
在WheelDbContext添加代码:
#region Permission
public DbSet<PermissionGrant> PermissionGrants { get; set; }
#endregion
void ConfigurePermissionGrants(ModelBuilder builder)
{
builder.Entity<PermissionGrant>(b =>
{
b.HasKey(o => o.Id);
b.Property(o => o.Permission).HasMaxLength(128);
b.Property(o => o.GrantValue).HasMaxLength(128);
b.Property(o => o.GrantType).HasMaxLength(32);
});
}
在OnModelCreating添加ConfigurePermissionGrants方法。
protected override void OnModelCreating(ModelBuilder builder)
{
base.OnModelCreating(builder);
ConfigurePermissionGrants(builder);
}
实现权限管理
接下来就是实现我们的权限管理功能。
我们的PermissionManageAppService只需定义三个API即可满足管理需求。分别是获取当前用户所有权限,修改用户权限,获取指定角色权限。
namespace Wheel.Services.PermissionManage
{
public interface IPermissionManageAppService : ITransientDependency
{
Task<R<List<GetAllPermissionDto>>> GetPermission();
Task<R> UpdatePermission(UpdatePermissionDto dto);
Task<R<List<GetAllPermissionDto>>> GetRolePermission(string RoleName);
}
}
具体实现代码如下:
namespace Wheel.Services.PermissionManage
{
public class PermissionManageAppService : WheelServiceBase, IPermissionManageAppService
{
private readonly IBasicRepository<PermissionGrant, Guid> _permissionGrantRepository;
private readonly RoleManager<Role> _roleManager;
private readonly XmlCommentHelper _xmlCommentHelper;
public PermissionManageAppService(XmlCommentHelper xmlCommentHelper, IBasicRepository<PermissionGrant, Guid> permissionGrantRepository, RoleManager<Role> roleManager)
{
_xmlCommentHelper = xmlCommentHelper;
_permissionGrantRepository = permissionGrantRepository;
_roleManager = roleManager;
}
public async Task<R<List<GetAllPermissionDto>>> GetPermission()
{
var result = await GetAllDefinePermission();
if (CurrentUser.IsInRoles("admin"))
{
result.ForEach(p => p.Permissions.ForEach(a => a.IsGranted = true));
}
else
{
var grantPermissions = (await _permissionGrantRepository
.SelectListAsync(a => a.GrantType == "R" && CurrentUser.Roles.Contains(a.GrantValue), a => a.Permission))
.Distinct().ToList();
foreach (var group in result)
{
foreach (var permission in group.Permissions)
{
if (grantPermissions.Any(b => b == $"{group.Group}:{permission.Name}"))
permission.IsGranted = true;
else
permission.IsGranted = false;
}
}
}
return new R<List<GetAllPermissionDto>>(result);
}
public async Task<R<List<GetAllPermissionDto>>> GetRolePermission(string RoleName)
{
var result = await GetAllDefinePermission();
var grantPermissions = (await _permissionGrantRepository
.SelectListAsync(a => a.GrantType == "R" && RoleName == a.GrantValue, a => a.Permission))
.Distinct().ToList();
foreach (var group in result)
{
foreach (var permission in group.Permissions)
{
if (grantPermissions.Any(b => b == $"{group.Group}:{permission.Name}"))
permission.IsGranted = true;
else
permission.IsGranted = false;
}
}
return new R<List<GetAllPermissionDto>>(result);
}
public async Task<R> UpdatePermission(UpdatePermissionDto dto)
{
if(dto.Type == "R")
{
var exsit = await _roleManager.RoleExistsAsync(dto.Value);
if (!exsit)
throw new BusinessException(ErrorCode.RoleNotExist, "RoleNotExist")
.WithMessageDataData(dto.Value);
}
using (var tran = await UnitOfWork.BeginTransactionAsync())
{
await _permissionGrantRepository.DeleteAsync(a => a.GrantType == dto.Type && a.GrantValue == dto.Value);
await _permissionGrantRepository.InsertManyAsync(dto.Permissions.Select(a=> new PermissionGrant
{
Id = GuidGenerator.Create(),
GrantType = dto.Type,
GrantValue = dto.Value,
Permission = a
}).ToList());
await DistributedCache.SetAsync($"Permission:{dto.Type}:{dto.Value}", dto.Permissions);
await tran.CommitAsync();
}
return new R();
}
private ValueTask<List<GetAllPermissionDto>> GetAllDefinePermission()
{
var result = MemoryCache.Get<List<GetAllPermissionDto>>("AllDefinePermission");
if (result == null)
{
result = new List<GetAllPermissionDto>();
var apiDescriptionGroupCollectionProvider = ServiceProvider.GetRequiredService<IApiDescriptionGroupCollectionProvider>();
var apiDescriptionGroups = apiDescriptionGroupCollectionProvider.ApiDescriptionGroups.Items.SelectMany(group => group.Items)
.Where(a => a.ActionDescriptor is ControllerActionDescriptor)
.GroupBy(a => (a.ActionDescriptor as ControllerActionDescriptor).ControllerTypeInfo);
foreach (var apiDescriptions in apiDescriptionGroups)
{
var permissionGroup = new GetAllPermissionDto();
var controllerTypeInfo = apiDescriptions.Key;
var controllerAllowAnonymous = controllerTypeInfo.GetAttribute<AllowAnonymousAttribute>();
var controllerComment = _xmlCommentHelper.GetTypeComment(controllerTypeInfo);
permissionGroup.Group = controllerTypeInfo.Name;
permissionGroup.Summary = controllerComment;
foreach (var apiDescription in apiDescriptions)
{
var method = controllerTypeInfo.GetMethod(apiDescription.ActionDescriptor.RouteValues["action"]);
var actionAllowAnonymous = method.GetAttribute<AllowAnonymousAttribute>();
var actionAuthorize = method.GetAttribute<AuthorizeAttribute>();
if ((controllerAllowAnonymous == null && actionAllowAnonymous == null) || actionAuthorize != null)
{
var methodComment = _xmlCommentHelper.GetMethodComment(method);
permissionGroup.Permissions.Add(new PermissionDto { Name = method.Name, Summary = methodComment });
}
}
if (permissionGroup.Permissions.Count > 0)
result.Add(permissionGroup);
}
MemoryCache.Set("AllDefinePermission", result);
}
return ValueTask.FromResult(result);
}
}
}
控制器代码如下:
namespace Wheel.Controllers
{
/// <summary>
/// 权限管理
/// </summary>
[Route("api/[controller]")]
[ApiController]
public class PermissionManageController : WheelControllerBase
{
private readonly IPermissionManageAppService _permissionManageAppService;
public PermissionManageController(IPermissionManageAppService permissionManageAppService)
{
_permissionManageAppService = permissionManageAppService;
}
/// <summary>
/// 获取所有权限
/// </summary>
/// <returns></returns>
[HttpGet()]
public Task<R<List<GetAllPermissionDto>>> GetPermission()
{
return _permissionManageAppService.GetPermission();
}
/// <summary>
/// 获取指定角色权限
/// </summary>
/// <returns></returns>
[HttpGet("{role}")]
public Task<R<List<GetAllPermissionDto>>> GetRolePermission(string role)
{
return _permissionManageAppService.GetRolePermission(role);
}
/// <summary>
/// 修改权限
/// </summary>
/// <param name="dto"></param>
/// <returns></returns>
[HttpPut]
public async Task<R> UpdatePermission(UpdatePermissionDto dto)
{
return await _permissionManageAppService.UpdatePermission(dto);
}
}
}
通过读取XML注释文件,自动生成Controller和Action的注释名称。
将权限配置信息写入缓存,提供给PermissionChecker使用。
权限返回的结构如下:
namespace Wheel.Services.PermissionManage.Dtos
{
public class GetAllPermissionDto
{
public string Group { get; set; }
public string Summary { get; set; }
public List<PermissionDto> Permissions { get; set; } = new ();
}
public class PermissionDto
{
public string Name { get; set; }
public string Summary { get; set; }
public bool IsGranted { get; set; }
}
}
使用Postman测试API,可以看到,获取了我们的权限信息列表,按照Controller分组,细分到每一个Action,summary是我们XML注释的内容。
到这我们就完成了权限管理的API。
轮子仓库地址https://github.com/Wheel-Framework/Wheel
欢迎进群催更。