关于 c#:使业务层的方法安全。最佳实践/最佳模式

Make a Method of the Business Layer secure. best practice / best pattern

我们正在使用带有大量 AJAX"页面方法"调用的 ASP.NET。
Page 中定义的 WebServices 从我们的 BusinessLayer 调用方法。
为了防止黑客调用页面方法,我们希望在 BusinessLayer 中实现一些安全性。

我们正在努力解决两个不同的问题。

第一个:

1
2
3
4
public List<Employees> GetAllEmployees()
{
    // do stuff
}

此方法应由具有"HR"角色的授权用户调用。

第二个:

1
2
3
4
public Order GetMyOrder(int orderId)
{
    // do sutff
}

该方法只能由订单的所有者调用。

我知道为每个方法实现安全性很容易,例如:

1
2
3
4
public List<Employees> GetAllEmployees()
{
    // check if the user is in Role HR
}

1
2
3
4
public Order GetMyOrder(int orderId)
{
    // check if the order.Owner = user
}

我正在寻找以通用方式实现这种安全性的一些模式/最佳实践(无需每次都对 if then else 进行编码)
我希望你明白我的意思:-)


用户@mdma 描述了一些关于面向方面的编程。为此,您将需要使用外部库(例如出色的 PostSharp),因为 .NET 没有太多 AOP 功能。但是,.NET 已经具有基于角色的安全性的 AOP 机制,可以解决您的部分问题。请看以下标准 .NET 代码示例:

1
2
3
4
5
[PrincipalPermission(SecurityAction.Demand, Role="HR")]
public List<Employees> GetAllEmployees()
{
    // do stuff
}

PrincipalPermissionAttribute 是 System.Security.Permissions 命名空间的一部分,也是 .NET 的一部分(自 .NET 1.0 起)。我多年来一直在使用它来在我的 Web 应用程序中实现基于角色的安全性。这个属性的好处是 .NET JIT 编译器在后台为您完成所有编织,您甚至可以在类级别定义它。在这种情况下,该类型的所有成员都将继承该属性及其安全设置。

当然它有它的局限性。您的第二个代码示例无法使用基于 .NET 角色的安全属性来实现。我认为您无法在此方法中真正解决一些自定义安全检查,或者调用一些内部安全库。

1
2
3
4
5
public Order GetMyOrder(int orderId)
{
    Order o = GetOrderInternal(orderId);
    BusinessSecurity.ValidateOrderForCurrentUser(o);
}

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

当您返回一个对象集合并想要过滤掉当前用户没有适当权限的所有对象时,LINQ 表达式树可以派上用场:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
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 通过表达式树(例如 NHibernate、LINQ to SQL 和实体框架)支持 LINQ 时,您可以编写一次这样的安全方法并将其应用到任何地方。当然,这样做的好处是,对数据库的查询将始终是最佳的。换句话说,不会检索到超出需要的记录。

更新(多年后):

我在我的代码库中使用了这个属性很长一段时间,但几年前,我得出的结论是基于属性的 AOP 有可怕的缺点。例如,它阻碍了可测试性。由于安全代码与普通代码交织在一起,因此您无法在不冒充有效用户的情况下运行普通单元测试。这很脆弱,不应该成为单元测试的关注点(单元测试本身违反了单一职责原则)。除此之外,它还迫使您在代码库中乱扔该属性。

因此,我不使用 PrincipalPermissionAttribute,而是通过使用装饰器package代码来应用横切关注点,例如安全性。这使我的应用程序更加灵活并且更容易测试。在过去的几年里,我写过几篇关于这项技术的文章(例如这篇和这篇)。


一个"最佳实践"是实现安全方面。这使安全规则与主要业务逻辑分开,避免了硬编码,并便于在不同环境中更改安全规则。

下面的文章列出了 7 种实现方面和保持代码分离的方法。一种简单且不会更改业务逻辑接口的方法是使用代理。这公开了与您当前拥有的相同接口,但允许替代实现,它可以装饰现有实现。可以使用硬编码或自定义属性将安全要求注入此接口。代理拦截对业务层的方法调用并调用适当的安全检查。此处详细描述了通过代理实现拦截 - Decouple Components by Injecting Custom Services into your Object's Invocation Chain。了解 .NET 中的 AOP 中给出了其他 AOP 方法。

这是一个讨论安全性的论坛帖子,它使用建议和安全属性来实现。最终结果是

1
2
3
4
5
6
7
8
9
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();

你可以把属性直接放在你的业务方法上,紧耦合,或者用这些属性创建一个服务代理,所以安全细节是分开的。


如果您使用 SOA,您可以创建一个安全服务,每个操作(方法)都会发送它的上下文(UserId、OrderId 等)。安全服务了解业务安全规则。

方案可能是这样的

1
UI -> Security -> BLL -> DAL