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

时间:2012-06-24 16:02:04

标签: c# asp.net asp.net-mvc-3 authentication forms-authentication

我已经遇到了一个绊脚石,我认为这是一个非常常见的任务。您在哪里验证是否允许登录用户编辑一段数据。在这种情况下,User具有Id,EmailAddress,Name和Address个对象的集合。 Address包含多个属性,一个是UserId,即它所属的用户的id。应仅允许用户编辑属于它们的Address个对象。编辑Address时/在哪里检查用户是否允许这样做。

我本周刚刚学习了ASP.NET MVC3并且正在实践它。我创建了一个Web应用程序,使用默认的MembershipProvider将用户登录(我将在以后将其替换为自定义的)。最终结果是登录后,控制器中的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层方法。因此,我让Controller与服务进行对话,该服务与业务逻辑层进行通信,业务逻辑层与存储库进行通信,最终使用Entity Framework 4与数据库进行通信。授权检查应该在哪里进行?

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

创意1

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

优点 - 简单实现,只需在服务返回Address对象后添加额外的if语句。 Controller可以访问User.Identity.Name。

缺点 - 数据被返回给控制器,所有这些都让控制器决定用户无法看到它。像“只有用户可以编辑自己的地址”这样的商业逻辑感觉已经进入控制器。

创意2

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

优势 - 用户仅编辑其详细信息的业务逻辑位于业务层中。

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

创意3

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

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

优势 - 用户仅编辑其详细信息的业务逻辑位于业务层中。无需将userId传递给每个方法调用。

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

创意4

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

2 个答案:

答案 0 :(得分:1)

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

答案 1 :(得分:1)

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

Principal HttpContext.Current.User

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

为了进一步将业务逻辑与对此静态成员的调用分离,我建议使用Adapter pattern来包装调用并检索用户的原则。这样,如果你决定放弃成员资格而不是其他一些身份验证/授权框架,或者你选择模拟它,那么就没有与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();
    }
}