Make a Method of the Business Layer secure. best p

2019-03-15 21:42发布

问题:

We are using ASP.NET with a lot of AJAX "Page Method" calls. The WebServices defined in the Page invokes methods from our BusinessLayer. To prevent hackers to call the Page Methods, we want to implement some security in the BusinessLayer.

We are struggling with two different issues.

First one:

public List<Employees> GetAllEmployees()
{
    // do stuff
}

This Method should be called by Authorized Users with the Role "HR".

Second one:

public Order GetMyOrder(int orderId)
{
    // do sutff
}

This Method should only be called by the owner of the Order.

I know it's easy to implement the security for each method like:

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

or

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

What I'm looking for is some pattern/best practice to implement this kind of security in a generic way (without coding the the if then else every time) I hope you get what i mean :-)

回答1:

User @mdma describes a bit about Aspect Oriented Programming. For this you will need to use an external library (such as the great PostSharp), because .NET doesn’t have much AOP functionality. However, .NET already has a AOP mechanism for role based security, that can solve part of your problem. Look at the following example of standard .NET code:

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

The PrincipalPermissionAttribute is part of the System.Security.Permissions namespace and is part of .NET (since .NET 1.0). I’ve been using it for years already to implement role based security in my web applications. Nice thing about this attribute is that the .NET JIT compiler does all the weaving for you on the background and you can even define it on a class level. In that case all members of that type will inherit that attribute and its security settings.

Of course it has its limitations. Your second code sample can't be implemented using the .NET role based security attribute. I think you can’t really come around some custom security checks in this method, or calling some internal security library.

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

Of course you can use an AOP framework, but you would still have to write an framework specific attribute that will again call your own security layer. This would only get useful when such an attribute would replace multiple method calls, for instance when having to put code inside try,catch,finally statements. When you would be doing a simple method call, there wouldn’t be much difference between a single method call or a single attribute IMO.

When you are returning a collection of objects and want to filter out all objects for which the current user doesn't have the proper rights, LINQ expression trees can come in handy:

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; 
    }
}

When your O/RM supports LINQ through expression trees (such as NHibernate, LINQ to SQL and Entity Framework) you can write such a security method once and apply it everywhere. Of course the nice thing about this is, that the query to your database will always be optimal. In other words, no more records will be retrieved than needed.

UPDATE (years later):

I used this attribute for a long time in my code base, but several years back, I came to the conclusion that attribute based AOP has terrible downsides. For instance, it hinders testability. Since security code is weaved with normal code, you can't run normal unit tests without having to impersonate a valid user. This is brittle and should not be a concern of the unit test (the unit test itself violates the Single Responsibility Principle). Besides that, it forces you to litter your code base with that attribute.

So instead of using the PrincipalPermissionAttribute, I rather apply cross-cutting concerns like security by wrapping code with decorators. This makes my application much more flexible and much easier to test. I've written several articles about this technique the last couple of years (for instance this one and this one).



回答2:

One "best practice" is to implement Security an aspect. This keeps the security rules separate from the primary business logic, avoiding hard-coding and making it easy to change the security rules in different environments.

The article below lists 7 ways of implementing aspects and keeping the code separate. One approach that is simple and doesn't change your business logic interface is to use a proxy. This exposes the same interface as you have currently, yet allows an alternative implementation, which can decorate the existing implementation. The security requirements can be injected into this interface, using either hard-coding or custom attributes. The proxy intercepts method calls to your business layer and invokes the appropriate security checks. Implementing interception via proxies is described in detail here - Decouple Components by Injecting Custom Services into your Object's Invocation Chain. Other AOP approaches are given in Understanding AOP in .NET.

Here's a forum post discussing security as an aspect, with implementation using advice and security attributes. The end result is

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();

You can put the attribute directly on your business method, tight coupling, or create a service proxy with these attributes, so the security details are kept separate.



回答3:

If you are using SOA, you can create a Security Service, and each action (method) will send it's context (UserId, OrderId etc.). Security Service knows about business security rules.

Scheme may be something like this

UI -> Security -> BLL -> DAL