我在哪个方向上遇到了一些绊脚石,我认为这是一项非常常见的任务。在哪里验证是否允许登录用户编辑一段数据。在这种情况下,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 加载它。