c# - 如何设计发布请求的多角色授权?
问题描述
在我正在构建的系统中,有一个复杂且不断变化的基于资源的授权。目前一共有六个角色。
该系统正在处理成员,所有成员都可以在他们自己的个人资料上编辑基本信息,另一个角色的另一个人可以在他们的个人资料上编辑更多信息等等。
我不知道哪种方法是用端点/操作来设计这个的最佳方法,比如编辑成员操作。我最终做了但不喜欢的是每个角色都有一个控制器操作、视图和视图模型。这样做而不是拥有一个视图模型的主要原因是,我觉得拥有某人甚至无法编辑的所有属性是没有意义的,那是过度发布,对吗?
我对结果不太满意。6 个视图模型、6 个视图、6 个非常相似的控制器操作、6 个验证器等。
我现在的想法是,当映射回域对象、视图和验证器类时,我将只有一个编辑操作,然后有一堆 if 语句。过度发布仍然存在,但使用 if 语句进行管理。我也是这么想的——如果系统变成API怎么办?api/members/1/edit/
比api/members/1/editAsTreasurer
?
你怎么看?有人有我没有想到的另一种解决方案吗?
一些代码部分,重复代码示例,当然在验证器类、视图和映射中还有更多,不确定要包含多少:
[HttpPost]
public IActionResult EditAsSecretary(EditMemberAsSecretaryViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
return View("EditAsSecretary", viewModel);
}
var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
if (member == null) return NotFound();
// Authorize
if (!_authorizationProvider.Authorize(viewModel.MemberInfo.LogeId, AdminType.Sekreterare))
return Forbid();
var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);
var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
!member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;
_domainLogger.UpdateLog(viewModel, member, user);
UpdateMember(viewModel, member, user.Id);
_unitOfWork.Complete();
if (finallyEmail) SendUserResetPasswordMail(member).Wait();
TempData["Message"] = "Member has been updated.";
return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}
[HttpPost]
public IActionResult EditAsManager(EditMemberAsManagerViewModel viewModel)
{
if (!ModelState.IsValid)
{
viewModel.Init(_basicDataProvider, _authorizationProvider.GetAuthorizedLogesForManageMember());
return View("EditAsManager", viewModel);
}
var member = _unitOfWork.Members.GetByMemberNumber(viewModel.MemberNumber, true);
if (member == null) return NotFound();
// Authorize
if (!_authorizationProvider.Authorize(member.LogeId, AdminType.Manager))
return Forbid();
var user = _unitOfWork.Members.GetByUserName(User.Identity.Name);
var finallyEmail = viewModel.MemberContactInfo.Email != null && member.Email == null &&
!member.HasBeenSentResetPasswordMail && member.MemberNumber != user.MemberNumber;
_domainLogger.UpdateLog(viewModel, member, user);
UpdateMember(viewModel, member, user.Id);
_unitOfWork.Complete();
if (finallyEmail) SendUserResetPasswordMail(member).Wait();
TempData["Message"] = "Member has been updated.";
return RedirectToAction("Details", "Members", new { memberNumber = member.MemberNumber });
}
private void UpdateMember(EditMemberAsSecretaryViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId)
{
_mapper.Map(viewModel, member);
MapGodfathers(viewModel.MemberInfo, member);
AfterUpdateMember(member, userId);
_userManager.UpdateNormalizedEmailAsync(member).Wait();
}
解决方案
我现在的想法是,当映射回域对象、视图和验证器类时,我将只有一个编辑操作,然后有一堆 if 语句。过度发布仍然存在,但使用 if 语句进行管理
不。
除了使代码的可读性大大降低之外,它还带来了安全风险。每个动作都应该尽可能少地使用它需要的参数。拥有更多操作不会花费您任何费用,因此没有理由这样做。
尽管您的代码存在一些问题,但这有助于重复:
您似乎正在对从用户那里收到的内容进行安全验证,而不是使用当前经过身份验证的用户。这是一个大问题,因为您信任来自用户的数据。
取而代之的是,创建一个自定义授权策略,使用您的业务逻辑检查用户类型。然后可以将它们添加到内置容器中,您可以使用:[Authorize(Policy = "EnsureManager")] public IActionResult EditAsManager(...)
这将允许您删除所有重复的代码并更接近 SRP。
你的重复
UpdateMember
看起来你的模型是不相关的。在这种情况下,最好有一个基本模型,然后是具有所需属性的子模型:public abstract class EditMemberBaseViewModel { [Required] public Something Something { get; set; } } public class EditMemberAsSecretaryViewModel : EditMemberBaseViewModel { [Required] public AnotherThing AnotherThing { get; set; } }
这将允许你有一个单一
UpdateMember
的,因为逻辑是基于EditMemberBaseViewModel
而不是他们的孩子,据你所表明的是:private void UpdateMember(EditMemberAsManagerViewModel viewModel, Member member, string userId) { _mapper.Map(viewModel, member); MapGodfathers(viewModel.MemberInfo, member); AfterUpdateMember(member, userId); _userManager.UpdateNormalizedEmailAsync(member).Wait(); }
最后一点,可能也是最重要的一点,这段代码有一个问题:
_userManager.UpdateNormalizedEmailAsync(member).Wait();
这真的很糟糕。您正在让 ASP.NET Core 挂起一个等待该操作完成的整个线程。这是同步的,2000 年代的代码。
您需要学习在应用程序中为每个与 IO 相关的操作(如数据库调用)使用异步代码,否则性能会受到很大影响。举个例子:
public async Task<IActionResult> EditAsManager(...)
{
.....
await UpdateMemberAsync(...);
}
public async Task UpdateMemberAsync(...)
{
await _userManager.UpdateNormalizedEmailAsync(member);
}
推荐阅读
- python - 如何使用 SimpleITK“LabelOverlapMeasuresImageFilter”在 Python 上使用 NiftiImages 提取度量
- angular - 使用从服务中获取的数据初始化响应式表单
- google-cloud-sql - 仅使用 ssl 的 Google Cloud Sql 私有 IP
- python - Python:如何将包含 .txt 文件的目录的所有文本内容输出为列表?
- java - SSLConnectionSocketFactory 只有两个并行的 https 调用在外面
- java - 在 spring 应用程序中启动 prometheus
- google-data-studio - 如何创建超过 6 个指标值的 Google 数据工作室雷达图?
- local-storage - 如何从不同的本地计算机连接到远程服务器上的 JIRA 应用程序?
- c# - 从 Azure 私有存储容器中获取 Blob
- xamarin.forms - 无法在 iOS 13 中安装设备代理