我们正在使用ASP.NET进行大量的AJAX“Page Method”调用。 页面中定义的Web服务调用BusinessLayer中的方法。 为了防止黑客调用页面方法,我们希望在BusinessLayer中实现一些安全性。
我们正在努力解决两个不同的问题。
第一个:
public List<Employees> GetAllEmployees()
{
// do stuff
}
此方法应由具有“HR”角色的授权用户调用。
第二个:
public Order GetMyOrder(int orderId)
{
// do sutff
}
此方法只能由订单所有者调用。
我知道为每种方法实现安全性很容易,例如:
public List<Employees> GetAllEmployees()
{
// check if the user is in Role HR
}
或
public Order GetMyOrder(int orderId)
{
// check if the order.Owner = user
}
我正在寻找的是以通用方式实现这种安全性的一些模式/最佳实践(每次都不编码if if else) 我希望你明白我的意思: - )
答案 0 :(得分:9)
用户@mdma描述了一些关于面向方面编程的内容。为此,您需要使用外部库(例如伟大的PostSharp),因为.NET没有太多的AOP功能。但是,.NET已经具有基于角色的安全性的AOP机制,可以解决部分问题。请查看以下标准.NET代码示例:
[PrincipalPermission(SecurityAction.Demand, Role="HR")]
public List<Employees> GetAllEmployees()
{
// do stuff
}
PrincipalPermissionAttribute是System.Security.Permissions命名空间的一部分,是.NET的一部分(自.NET 1.0起)。多年来我一直在使用它来在我的Web应用程序中实现基于角色的安全性。关于这个属性的好处是.NET JIT编译器在后台为你完成所有的编织,你甚至可以在类级别上定义它。在这种情况下,该类型的所有成员都将继承该属性及其安全设置。
当然它有其局限性。您的第二个代码示例无法使用基于.NET角色的安全属性实现。我认为你不能真正在这个方法中进行一些自定义安全检查,或者调用一些内部安全库。
public Order GetMyOrder(int orderId)
{
Order o = GetOrderInternal(orderId);
BusinessSecurity.ValidateOrderForCurrentUser(o);
}
当然,您可以使用AOP框架,但您仍然需要编写一个特定于框架的属性,该属性将再次调用您自己的安全层。只有当这样的属性替换多个方法调用时才会有用,例如当必须将代码放在try,catch,finally语句中时。当您进行简单的方法调用时,单个方法调用或单个属性IMO之间没有太大区别。
当您返回一组对象并希望过滤掉当前用户没有适当权限的所有对象时,LINQ表达式树可以派上用场:
public Order[] GetAllOrders()
{
IQueryable orders = GetAllOrdersInternal();
orders = BusinessSecurity.ApplySecurityOnOrders(orders);
return orders.ToArray();
}
static class BusinessSecurity
{
public static IQueryable<Order> ApplySecurityOnOrders(
IQueryable<Order> orders)
{
var user = Membership.GetCurrentUser();
if (user.IsInRole("Administrator"))
{
return orders;
}
return
from order in orders
where order.Customer.User.Name == user.Name
select order;
}
}
当您的O / RM支持LINQ到表达式树(例如NHibernate,LINQ to SQL和Entity Framework)时,您可以编写一次这样的安全方法并将其应用到任何地方。当然,关于这一点的好处是,对数据库的查询将始终是最佳的。换句话说,不会检索到比需要更多的记录。
更新(多年后):
我在代码库中使用了这个属性很长一段时间,但几年前,我得出结论,基于属性的AOP有可怕的缺点。例如,它阻碍了可测试性。由于安全代码是使用普通代码编织的,因此无需模拟有效用户就无法运行正常的单元测试。这很脆弱,不应该成为单元测试的关注点(单元测试本身违反了单一责任原则)。除此之外,它会强迫您使用该属性丢弃代码库。
因此,我不是使用PrincipalPermissionAttribute
,而是通过使用decorators包装代码来应用跨安全问题,例如安全性。这使我的应用程序更灵活,更容易测试。我在过去几年写了几篇关于这种技术的文章(例如this one和this one)。
答案 1 :(得分:2)
一个“最佳实践”是实现安全性方面。这使安全规则与主要业务逻辑分离,避免了硬编码,并且可以轻松地在不同环境中更改安全规则。
下面的文章列出了实现方面和保持代码分离的7种方法。一种简单且不会改变业务逻辑接口的方法是使用代理。这暴露了与您当前相同的接口,但允许替代实现,可以装饰现有实现。可以使用硬编码或自定义属性将安全要求注入此接口。代理拦截对业务层的方法调用,并调用相应的安全检查。这里详细描述了通过代理实现拦截 - Decouple Components by Injecting Custom Services into your Object's Invocation Chain。其他AOP方法在Understanding AOP in .NET中给出。
这是一个forum post讨论安全性作为一个方面,使用建议和安全属性实现。最终结果是
public static class Roles
{
public const string ROLE_ADMIN = "Admin";
public const string ROLE_CONTENT_MANAGER = "Content Manager";
}
// business method
[Security(Roles.ROLE_HR)]
public List<Employee> GetAllEmployees();
您可以将属性直接放在业务方法,紧密耦合或使用这些属性创建服务代理,因此安全性详细信息将保持独立。
答案 2 :(得分:0)
如果您使用的是SOA,则可以创建安全服务,每个操作(方法)都会发送它的上下文(UserId,OrderId等)。安全服务了解业务安全规则。
Scheme可能是这样的
UI -> Security -> BLL -> DAL