【问题标题】:Multi User App with MVC3, ASP.NET Membership - User Authentication/ Data Separation具有 MVC3、ASP.NET 成员资格的多用户应用程序 - 用户身份验证/数据分离
【发布时间】:2011-03-04 14:29:28
【问题描述】:

我正在使用 ASP.NET MVC3 和 EF4、一个数据库、一个代码库构建一个简单的多用户(多租户?)应用程序,所有用户都使用相同的 URL 访问该应用程序。一旦用户登录,他们应该只能访问他们的数据,我使用默认的 asp.NET 成员资格提供程序,并在每个数据表上添加了一个“UserId”Guid 字段。显然我不希望用户 A 对用户 B 的数据有任何访问权限,因此我几乎在控制器上的每个操作中都添加了以下内容。

public ActionResult EditStatus(int id)
    {
        if (!Request.IsAuthenticated)
            return RedirectToAction("Index", "Home");

        var status = sService.GetStatusById(id);

        // check if the logged in user has access to this status
        if (status.UserId != GetUserId())
            return RedirectToAction("Index", "Home");
    .
    .
    .
    }

    private Guid GetUserId()
    {
        if (Membership.GetUser() != null)
        {
            MembershipUser member = Membership.GetUser();
            Guid id = new Guid(member.ProviderUserKey.ToString());
            return id;
        }
        return Guid.Empty;
    }

这种重复肯定感觉不对,必须有一种更优雅的方式来确保我的用户无法访问彼此的数据——我错过了什么?

【问题讨论】:

    标签: asp.net-mvc-3 asp.net-membership


    【解决方案1】:

    我错过了什么?

    自定义模型绑定器:

    public class StatusModelBinder : DefaultModelBinder
    {
        public override object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)
        {
            // Fetch the id from the RouteData
            var id = controllerContext.RouteData.Values["id"] as string;
    
            // TODO: Use constructor injection to pass the service here
            var status = sService.GetStatusById(id);
    
            // Compare whether the id passed in the request belongs to 
            // the currently logged in user
            if (status.UserId != GetUserId())
            {
                throw new HttpException(403, "Forbidden");
            }
            return status;
        }
    
        private Guid GetUserId()
        {
            if (Membership.GetUser() != null)
            {
                MembershipUser member = Membership.GetUser();
                Guid id = new Guid(member.ProviderUserKey.ToString());
                return id;
            }
            return Guid.Empty;
        }
    }
    

    然后您将在Application_Start 中注册此模型绑定器:

    // Could use constructor injection to pass the repository to the model binder
    ModelBinders.Binders.Add(typeof(Status), new StatusModelBinder());
    

    最后

    // The authorize attribute ensures that a user is authenticated. 
    // If you want it to redirect to /Home/Index as in your original
    // example if the user is not authenticated you could write a custom
    // Authorize attribute and do the job there
    [Authorize]
    public ActionResult EditStatus(Status status)
    {
        // if we got that far it means that the user has access to this resource
        // TODO: do something with the status and return some view
        ...
    }
    

    结论:我们已经让这个控制器节食,这是控制器应该的方式:-)

    【讨论】:

    • 非常酷,不知道这个功能。使用此方法如何执行重定向?
    • @Jonathan Freeland,什么重定向?在 RESTful 应用程序中,您应该使用正确的状态代码来指示意图,而不是重定向。然后你可以有一个全局错误处理程序,它会捕获这些错误并呈现相应的视图,但不要重定向。我的意思是当用户被拒绝访问时发送 200 状态码是完全错误的。
    • 达林 - 太棒了 - 谢谢!! “然后你可以有一个全局错误处理程序来捕获这些错误并呈现各自的视图”你是否推荐我可以遵循的任何链接来了解更多关于以这种方式做事的更多信息?
    • @Simon Owen,你可以看看following question
    【解决方案2】:

    试图了解这个实现(我有完全相同的问题),我发现了 Scott Hanselman 的帖子中描述的类似方法

    http://www.hanselman.com/blog/IPrincipalUserModelBinderInASPNETMVCForEasierTesting.aspx

    
        public class IPrincipalModelBinder : IModelBinder
        {    
            public object BindModel(ControllerContext controllerContext, ModelBindingContext bindingContext)    
            {        
                if (controllerContext == null) 
                {            
                throw new ArgumentNullException("controllerContext");        
                }        
                if (bindingContext == null) 
                {            
                throw new ArgumentNullException("bindingContext");        
                }        
                IPrincipal p = controllerContext.HttpContext.User;        
                return p;    
            }
        }
    
    
    
        void Application_Start() 
        {    
            RegisterRoutes(RouteTable.Routes); //unrelated, don't sweat this line.    
            ModelBinders.Binders[typeof(IPrincipal)] = new IPrincipalModelBinder();
        }
    
        [Authorize]
        public ActionResult Edit(int id, IPrincipal user) 
        {     
            Dinner dinner = dinnerRepository.FindDinner(id);     
    
            if (dinner.HostedBy != user.Identity.Name)        
                return View("InvalidOwner");     
    
            var viewModel = new DinnerFormViewModel {        
                Dinner = dinner,        
                Countries = new SelectList(PhoneValidator.Countries, dinner.Country)    
            };     
            return View(viewModel);
        }
    

    对于像我这样的 MVC 菜鸟来说,这更容易理解。

    【讨论】:

      猜你喜欢
      • 1970-01-01
      • 1970-01-01
      • 2013-05-04
      • 1970-01-01
      • 1970-01-01
      • 2015-10-26
      • 1970-01-01
      • 1970-01-01
      • 2011-07-25
      相关资源
      最近更新 更多