使业务层方法更加安全的最佳实践/最佳模式。

9
我们正在使用大量 AJAX “Page Method” 调用的 ASP.NET。页面中定义的 WebServices 会调用我们的 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-then-else) 希望您理解我的意思 :-)

1
我看到你在SO上已经有一段时间了。不过,我建议你在标题中不要加入标签([.net/c#]),让它们留在标签中。此外,“Hi”和“Thanks”,虽然适用于讨论论坛,但并不适用于像SO这样的问答网站。谢谢。 - John Saunders
3个回答

8
用户@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以来)。我已经多年使用它来实现基于角色的安全性控制。这个属性的好处是,.NET JIT编译器会在后台为您完成所有编织工作,甚至可以在类级别上定义它。在这种情况下,该类型的所有成员都将继承该属性及其安全设置。

当然,它也有局限性。使用.NET基于角色的安全性属性无法实现第二个代码示例。我认为你不能真正避免一些自定义安全检查或调用一些内部安全库。

public Order GetMyOrder(int orderId)
{
    Order o = GetOrderInternal(orderId);
    BusinessSecurity.ValidateOrderForCurrentUser(o);
}

当然,你可以使用AOP框架,但是你仍然需要编写一个特定于框架的属性,该属性将再次调用您自己的安全层。只有当这样的属性可以替换多个方法调用时才会有用,例如在必须将代码放入try、catch、finally语句中时。当您进行简单的方法调用时,单个方法调用或单个属性之间没有太大区别,我认为。

当您返回对象集合并想要过滤掉当前用户没有适当权限的所有对象时,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,而是通过使用装饰器来应用横切关注点(如安全性)。这使我的应用程序更加灵活且更易于测试。在过去的几年中,我已经写了几篇关于这种技术的文章(例如这篇这篇)。

2
一种“最佳实践”是实现安全性方面。这将安全规则与主要业务逻辑分开,避免硬编码并使在不同环境中更改安全规则变得容易。
下面的文章列出了7种实现方面并保持代码分离的方法。一种简单且不改变您的业务逻辑接口的方法是使用代理。这公开与您当前拥有的相同接口,但允许替代实现,可以装饰现有实现。可以使用硬编码或自定义属性将安全要求注入此接口。代理拦截对业务层的方法调用并调用适当的安全检查。通过代理实现拦截的详细说明在此处描述 - 通过将自定义服务注入对象的调用链来解耦组件。其他AOP方法在了解.NET中的AOP中给出。
这里是一个论坛帖子,讨论安全作为方面的问题,并使用建议和安全属性进行实现。最终结果是...
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();

您可以直接将属性放在业务方法上,实现紧密耦合;或者使用这些属性创建服务代理,以便将安全详细信息保持分离。


在我看来,这个解决方案过于依赖框架(通过装饰方法)。我会将安全特性作为系统的一部分实现,因为它并不像看起来那样脱离业务逻辑。不过,我喜欢代理建议...这是我的意见。 - Mike Gleason jr Couturier
@Mike - 这些属性是你自己的,所以我不明白这与任何框架有什么关系。它们是一种实现细节 - 声明式安全而不是编写代码。你仍然可以自由地编写代码,但将代码放在业务对象本身上是OP想要避免的。当属性放在业务对象上时,它们仍然是“分离”的 - 即易于识别。如果你将安全检查作为代码并将其与业务逻辑放在一起,情况就不是这样了。 - mdma
嗨,我认为这就像是硬编码一样。这就是为什么我说“框架”的原因,很抱歉。如果角色是动态的,怎么办?我会创建安全对象,就像“用户”对象一样,因为它们和系统中的任何其他东西一样。 - Mike Gleason jr Couturier

0
如果您正在使用SOA,您可以创建一个安全服务,并且每个操作(方法)都会发送其上下文(UserId、OrderId等)。安全服务了解业务安全规则。
方案可能是这样的。
UI -> Security -> BLL -> DAL

1
我同意将安全性放在应用程序层而不是“框架”层,就像其他答案所提出的那样。如果角色是动态的,那该怎么办? - Mike Gleason jr Couturier

网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接