保护编辑用户详细信息的访问



我在哪个方向上遇到了一些绊脚石,我认为这是一项非常常见的任务。在哪里验证是否允许登录用户编辑一段数据。在这种情况下,User具有 ID、电子邮件地址、名称和Address对象的集合。Address包含多个属性,其中一个是 UserId,即它所属的用户的 ID。应仅允许用户编辑属于他们的Address对象。编辑Address时,您检查的时间/地点允许用户这样做。

本周我刚刚学习了 MVC3 ASP.NET 并正在将其付诸实践。我创建了一个 Web 应用程序,该应用程序使用默认的 MemberProvider 登录用户(稍后我将用自定义的替换它)。最终结果是,登录后,控制器中的 User.Identity.Name 属性返回用户的电子邮件地址。

如果用户想要查看自己的详细信息,我将Addresses操作调用AccountController。如下。

[Authorize]
public ActionResult Addresses()
{
    IEnumerable<Address> addresses = _myService.GetUserAddresss(User.Identity.Name));
    return View(addresses);
}
现在,如果用户想要编辑地址详细信息,他们可以调用操作来

获取地址并显示它,并调用操作来保存对地址的编辑。

[Authorize]
public ActionResult Address(int id)
{
    Address address= _myService.GetAddress(id));
    return View(address);
}
[Authorize]
[HttpPost]
public ActionResult Address(EditAddressModel model)
{
    Address address = _myService.SaveDetail(model.Address));
    return View(address );
}

上述方法的缺陷是,如果用户访问网址../Account/Address/12。然后,他们可以查看和编辑 id 为 12(如果存在)的Address,无论他们是否创建了它。

我遵循 N 层方法。因此,我让控制器与服务通信,该服务与业务逻辑层通信,与存储库通信,存储库最终使用实体框架 4 与数据库通信。授权检查应该在哪里进行?

我已经考虑了以下解决方案,但无法决定合适的方法。

想法1

在控制器类中,因为控制器有权访问 User.Identity.Name 属性。使用此属性,它可以检查从服务返回的地址中的用户 ID 是否与当前登录的用户匹配。如果没有,则显示错误页面,否则让他们正常查看/编辑。

优点 - 实现简单,只需在服务返回Address对象后添加一个额外的 if 语句即可。控制器有权访问 User.Identity.Name。

缺点 - 数据返回到控制器,所有数据都由控制器决定用户看不到它。感觉"只有用户可以编辑自己的地址"的业务逻辑已经潜入控制器。

想法2

在业务层中。控制器使用 userId 和 detailId (_myService.GetAddress(User.Identity.Name, detailId)); 调用服务,而 userId 和 detailId 又调用业务层。业务层有一个方法public Address GetAddress(int userId, int addressId)当请求业务层从数据库中获取Address时,它会检查返回的地址属于用户并返回。如果不是,则返回 null。因此,控制器将从服务获得空响应,并显示适当的错误消息。

优势 - 用户的业务逻辑只编辑其详细信息,是在业务层。

不利 - 由于业务逻辑无法访问 User.Identity.Name,因此服务和业务类中的每个方法都需要一个 userId 参数,这感觉是错误的和不必要的膨胀。

想法3

如上所述,除了服务和业务层类之外,具有一个名为 UserId 的属性。这用于检查用户是否可以访问数据库资源。这可以在创建期间或调用服务之前设置。即

[Authorize]
public ActionResult Address(int id)
{
    _myService.User = User.Identity.Name;
    Address address = _myService.GetAddress(id));
    return View(address);
}

优势 - 用户的业务逻辑只编辑其详细信息,是在业务层。无需将 userId 传递给每个方法调用。

不赞成 - 我从未见过使用这种方法的例子。我觉得这是不对的。并不是说我使用 WCF 作为服务层。但我敢肯定,如果我是,拥有这个额外的财产是行不通的。

想法4

访问业务层中的 User.Identity.Name,而无需通过服务将其从控制器传递到业务层。不确定是否可能。

我建议在业务逻辑层进行授权。 您可以在应用程序中的任何位置访问当前用户的主体(只要它是同一应用程序域)。 您需要做的就是访问静态成员 HttpContext.Current 并检索当前 HttpContext 的用户主体。

Principal HttpContext.Current.User

显然,包含此代码的程序集将需要引用 System.Web。 我认为这在 Web 应用程序中不应该是一个大问题。

为了进一步将业务逻辑与此静态成员的调用分离,我建议使用 Adapter 模式来包装调用并检索用户的原则。 这样,如果您决定放弃成员资格而支持其他身份验证/授权框架,或者您选择模拟它,那么您就没有与 HttpContext.Current 的具体耦合。

下面是用于访问当前上下文的用户原则的适配器示例。

public interface IUserAdapter
{
    IPrincipal GetUserPrincipal();
    void SetAuthenticationCookie(IUser user);
    void SignOut();
}
// my business logic representation of a user
public interface IUser
{
    int Id { get; set; }
    string Name { get; set; }
}
public class WebUserAdapter : IUserAdapter
{
    public IPrincipal GetUserPrincipal()
    {
        return HttpContext.Current.User;
    }
    public void SetAuthenticationCookie(IUser user)
    {
        FormsAuthentication.SetAuthCookie(user.Id.ToString(), false);
    }
    public void SignOut()
    {
        FormsAuthentication.SignOut();
    }
}

只需在每个查询中包含用户的 Id。你在想法二中已经做到了这一点。作为安全审计的一部分,如果作为团队的一部分执行此操作,请确保每个数据访问方法都包含参数,并在查询数据库的 where 子句中使用该用户 ID 或名称。集成测试可以帮助在这里为用户创建一个记录,并通过调用传入另一个用户名参数的方法尝试按 id 加载它。

最新更新