在控制器中重复授权代码的最佳实践

本文关键字:最佳 代码 授权 控制器 | 更新日期: 2023-09-27 17:49:38

我有一个控制器,该控制器具有GetSignatories(), AddMe(), RemoveMe(), AddUser(), RemoveUser()等方法,因此我必须每次验证合同是否存在,用户是否有权访问合同以及所请求的版本是否存在。我想知道我应该把这段代码放在哪里?在服务中还是在控制器的其他方法中提取?我的问题是,我有时返回ununauthorized或NotFound,不知道什么是最好的方法。

这是我的控制器:

public partial class ContractController : Controller
    {
        private ISession _session;
        private IAuthenticationService _authService;
        public V1Controller(ISession session, IAuthenticationService authService)
        {
            _session = session;
            _authService = authService;
        }
        [HttpPost]
        [Authorize(Roles = "User")]
        [ValidateAntiForgeryToken]
        public virtual ActionResult GetSignatories(string contractId, int version)
        {
            //NOTE Should be extracted
            var contract = _session.Single<Contract>(contractId);
            if (contract == null) return HttpNotFound();
            var user = _authService.LoggedUser();
            if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
            if (contract.Versions.Count < version) return HttpNotFound();
            /*NOTE  END Should be extracted */
            return Json(contract.CurrentVersion.Tokens.Select(x => x.User).ToList());
        }
        [HttpPost]
        [Authorize(Roles = "User")]
        [ValidateAntiForgeryToken]
        public virtual ActionResult AddMe(string contractId, int version)
        {
            //NOTE Should be extracted
            var contract = _session.Single<Contract>(contractId);
            if (contract == null) return HttpNotFound();
            var user = _authService.LoggedUser();
            if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
            if (contract.Versions.Count < version) return HttpNotFound();
            /*NOTE  END Should be extracted */
            return AddUserToContract(contract, new UserSummary(user));
        }
        [HttpPost]
        [Authorize(Roles = "User")]
        [ValidateAntiForgeryToken]
        public virtual ActionResult RemoveMe(string contractId, int version)
        {
            //NOTE Should be extracted
            var contract = _session.Single<Contract>(contractId);
            if (contract == null) return HttpNotFound();
            var user = _authService.LoggedUser();
            if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
            if (contract.Versions.Count < version) return HttpNotFound();
            /*NOTE  END Should be extracted */
            return RemoveUserFromContract(contract, new UserSummary(user));
        }
        [HttpPost]
        [Authorize(Roles = "User")]
        [ValidateAntiForgeryToken]
        public virtual ActionResult AddUser(string contractId, int version, UserSummary userSummary)
        {
            //NOTE Should be extracted
            var contract = _session.Single<Contract>(contractId);
            if (contract == null) return HttpNotFound();
            var user = _authService.LoggedUser();
            if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
            if (contract.Versions.Count < version) return HttpNotFound();
            /*NOTE  END Should be extracted */

            return AddUserToContract(contract, userSummary);
        }
        [HttpPost]
        [Authorize(Roles = "User")]
        [ValidateAntiForgeryToken]
        public virtual ActionResult RemoveUser(string contractId, int version, UserSummary userSummary)
        {
            //NOTE Should be extracted
            var contract = _session.Single<Contract>(contractId);
            if (contract == null) return HttpNotFound();
            var user = _authService.LoggedUser();
            if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue || contract.CreatedBy.Id.Value != user.Id) return new HttpUnauthorizedResult();
            if (contract.Versions.Count < version) return HttpNotFound();
            /*NOTE  END Should be extracted */

            return RemoveUserFromContract(contract, userSummary);
        }
}

对于那些可能正在寻找如何在全局中注册模型绑定器的人:

public static void RegisterModelBinders()
    {
        var session = (ISession)DependencyResolver.Current.GetService(typeof(ISession));
        var authService = (IAuthenticationService)DependencyResolver.Current.GetService(typeof(IAuthenticationService));
        System.Web.Mvc.ModelBinders.Binders[typeof (Contract)] = new ContractModelBinder(session, authService);
    }

在控制器中重复授权代码的最佳实践

确实有相当重复的代码。重构这段代码的方法有很多,其中之一就是为Contract模型编写一个自定义模型绑定器:

public class ContractModelBinder : DefaultModelBinder
{
    private readonly ISession _session;
    private readonly IAuthenticationService _authService;
    public ContractModelBinder(ISession session, IAuthenticationService authService)
    {
        _session = session;
        _authService = authService;
    }
    public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
    {
        string contractId = null;
        int version = 0;
        var contractIdValue = bindingContext.ValueProvider.GetValue("contractId");
        var versionValue = bindingContext.ValueProvider.GetValue("version");
        if (versionValue != null)
        {
            version = int.Parse(versionValue.AttemptedValue);
        }
        if (contractIdValue != null)
        {
            contractId = contractIdValue.AttemptedValue;
        }
        var contract = _session.Single<Contract>(contractId);
        if (contract == null)
        {
            throw new HttpException(404, "Not found");
        }
        var user = _authService.LoggedUser();
        if (contract.CreatedBy == null || 
            !contract.CreatedBy.Id.HasValue || 
            contract.CreatedBy.Id.Value != user.Id
        )
        {
            throw new HttpException(401, "Unauthorized");
        }
        if (contract.Versions.Count < version)
        {
            throw new HttpException(404, "Not found");
        }
        return contract;
    }
}

一旦你在Application_Start中用Contract模型注册了这个模型绑定器,你的控制器就变成了:

public class ContractController : Controller
{
    [HttpPost]
    [Authorize(Roles = "User")]
    [ValidateAntiForgeryToken]
    public ActionResult GetSignatories(Contract contract)
    {
        return Json(contract.CurrentVersion.Tokens.Select(x => x.User).ToList());
    }
    [HttpPost]
    [Authorize(Roles = "User")]
    [ValidateAntiForgeryToken]
    public ActionResult AddMe(Contract contract)
    {
        var user = contract.CreatedBy;
        return AddUserToContract(contract, new UserSummary(user));
    }
    [HttpPost]
    [Authorize(Roles = "User")]
    [ValidateAntiForgeryToken]
    public ActionResult RemoveMe(Contract contract)
    {
        var user = contract.CreatedBy;
        return RemoveUserFromContract(contract, new UserSummary(user));
    }
    [HttpPost]
    [Authorize(Roles = "User")]
    [ValidateAntiForgeryToken]
    public ActionResult AddUser(Contract contract, UserSummary userSummary)
    {
        return AddUserToContract(contract, userSummary);
    }
    [HttpPost]
    [Authorize(Roles = "User")]
    [ValidateAntiForgeryToken]
    public ActionResult RemoveUser(Contract contract, UserSummary userSummary)
    {
        return RemoveUserFromContract(contract, userSummary);
    }
}

我们成功地让它节食。

一个可能的解决方案是创建一个IContractService接口,该接口具有两个方法,一个用于获取合同,另一个用于验证合同:

public IContractService
{
    Contract GetContract(int id);
    ValidationResult ValidateContract(Contract contract);
}

ValidationResult可以是一个枚举,只是为了通知方法的调用者如何继续:

public enum ValidationResult
{
    Valid,
    Unauthorized,
    NotFound
}

IContractService的可能实现:

public class ContractService : IContractService
{
    private readonly ISession _session;
    private readonly IAuthenticationService _authService;
    // Use Dependency Injection for this!
    public ContractService(ISession session, IAuthenticationService authService)
    {
       _session = session;
       _authService = authService;
    }
    public Contract GetContract(int id)
    {
        var contract = _session.Single<Contract>(contractId);
        // hanlde somwhere else whether it's null or not
        return contract;
    }
    public ValidationResult ValidateContract(Contract contract)
    {
        var user = _authService.LoggedUser();
        if (contract.CreatedBy == null || !contract.CreatedBy.Id.HasValue ||
            contract.CreatedBy.Id.Value != user.Id) 
              return ValidationResult.Unauthorized;
        if (contract.Versions.Count < version) 
            return ValidationResult.NotFound;
        return ValidationResult.Valid;
    }
}

然后在你的控制器中,你仍然可以返回各种HttpNotFound等到视图:

[HttpPost, Authorize(Roles = "User"), ValidateAntiForgeryToken]
public virtual ActionResult GetSignatories(string contractId, int version)
{
    //NOTE Should be extracted
    var contract = _contractService.GetContract(contractId);
    if (contract == null) 
        return HttpNotFound();
    var result = _contractService.ValidateContract(contract);
    if (result == ValidationResult.Unauthorized) 
        return new HttpUnauthorizedResult();
    if (result == ValidationResult.NotFound) 
        return HttpNotFound();
    return Json(contract.CurrentVersion.Tokens.Select(x => x.User).ToList());
}