C# 如何对post请求设计多角色授权?

C# 如何对post请求设计多角色授权?,c#,asp.net-core-mvc,authorization,C#,Asp.net Core Mvc,Authorization,在我正在构建的系统中,有一个复杂且不断变化的基于资源的授权。目前共有六个角色 该系统正在处理成员,所有成员都可以在自己的个人资料上编辑基本信息,另一个角色的其他人可以在他们的个人资料上编辑更多信息,等等 我不知道用端点/操作(如编辑成员操作)设计帖子的最佳方法是什么。我最后做的,但不喜欢的是,每个角色都有一个控制器动作、视图和视图模型。这样做而不是拥有一个视图模型的主要原因是,我觉得拥有一些人甚至不能编辑的属性是没有意义的,这太过分了,对吗 我对结果不太满意。6个视图模型,6个视图,6个极其相似

在我正在构建的系统中,有一个复杂且不断变化的基于资源的授权。目前共有六个角色

该系统正在处理成员,所有成员都可以在自己的个人资料上编辑基本信息,另一个角色的其他人可以在他们的个人资料上编辑更多信息,等等

我不知道用端点/操作(如编辑成员操作)设计帖子的最佳方法是什么。我最后做的,但不喜欢的是,每个角色都有一个控制器动作、视图和视图模型。这样做而不是拥有一个视图模型的主要原因是,我觉得拥有一些人甚至不能编辑的属性是没有意义的,这太过分了,对吗

我对结果不太满意。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核心挂起整个线程,等待该操作完成。这是同步的,2000年代的代码。 您需要学习为应用程序中的每个与IO相关的操作(如数据库调用)使用异步代码,否则性能将受到很大影响。例如:

public async Task<IActionResult> EditAsManager(...)
{
    .....
    await UpdateMemberAsync(...);
}

public async Task UpdateMemberAsync(...)
{
    await _userManager.UpdateNormalizedEmailAsync(member);
}

@我已经添加了代码示例。好的,谢谢你,很好的输入!安全验证在使用授权提供程序类IHttpContextAccessor的内部对当前用户运行。。我认为策略是不够的,因为授权是基于资源的——我需要知道要编辑的成员属于哪个loge来授权。我实际上已经在使用继承,但出于某种原因仍然重用了该代码。是的,你理解我了,我一点都不懂异步哈哈,谢谢。如果这是一个API,你会使用API/members/1/editAsManager API/members/1/EditasCretary或API/members/1/edit,每个角色一个命令对象吗?@Lindeberg我觉得这不像REST。我会使用类似于PUT-api/members/1/manager和PUT-api/members/1/secretary的东西,但不要相信我的话,这是我的观点。您应该花点时间学习c中异步代码的基础知识,这将使您在开发技能方面走得更远@Lindeberg,我想知道这个答案是否对您有所帮助。你可能想读书