我已经遇到了一个绊脚石,我认为这是一个非常常见的任务。您在哪里验证是否允许登录用户编辑一段数据。在这种情况下,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,而不必通过控制器将服务传递给业务层。不确定是否可能。
答案 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();
}
}