1

在我认为是一项非常常见的任务的方向上,我遇到了一些绊脚石。您在哪里验证登录用户是否被允许编辑一条数据。在这种情况下,aUser有一个 Id、EmailAddress、Name 和一个Address对象集合。AnAddress包含几个属性,一个是 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 );
}

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

我正在遵循 N 层方法。因此,我让控制器与服务对话,该服务与业务逻辑层对话,该服务与存储库对话,最后使用 Entity Framework 4 与数据库对话。授权检查应该在哪里进行?

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

理念一

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

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

缺点 - 数据返回给控制器,由控制器决定用户看不到它。感觉就像“只有用户可以编辑自己的地址”的业务逻辑已经渗透到控制器中。

想法 2

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

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

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

想法 3

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

[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,而无需将其通过服务从控制器传递到业务层。不确定是否可能。

4

2 回答 2

1

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

于 2012-06-24T16:48:53.550 回答
1

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

主体 HttpContext.Current.User

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

为了进一步将您的业务逻辑与对该静态成员的调用分离,我建议使用适配器模式来包装调用并检索用户的原则。这样,如果您决定放弃 Membership 以支持其他一些身份验证/授权框架,或者您选择模拟它,您就没有与 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();
    }
}
于 2012-06-24T16:50:00.263 回答