Document-Based Security in ASP.NET MVC

2019-02-02 01:18发布

问题:

I already know about User and Role-based security in ASP.NET MVC. But now I need something a little more granular.

Let's say I have a list of documents, some of which the user is authorized for, some not. Each document has a corresponding record in a documents table in a database. Documents can be downloaded for viewing, if the user has security access. Documents can also be added, if you have the role. Each document has an URL, and each document list has an URL.

I would like to security trim the list so that the user only sees those documents for which he is authorized. But I also need to authenticate the URL requests for these lists and documents, since there is nothing preventing a user from bookmarking a document they no longer have access to, or simply typing the URL into the browser.

Is the built-in role-based security model suitable for this, or do I need to create separate, table-based security? Can I put the security in my repository, so that the returned records are already trimmed, or should it be part of the controller? Do I need a security attribute to validate the controller request, or should I just put it in the controller method as the first few lines of code?

回答1:

@Robert, I think you've already answered your own question when you said you should trim them (before) they reach the view. So in your Business logic, as a preference over the repository, you might want to do a lamda to trim off the excess so to speak.

Im my opinion I would never return any records to the view that the user wasn't allowed to see. Why increase risk and traffic?

As for the bookmarks I think there you're going to need to do some business logic preventing them from going to the url when access no longer exists.

I thought the controller was simply there to service the data to the page and not to have any logic as such so I'd prefer the business layer approach for this one as it does appear to be a business rule.

That might not be what you had in mind but unless there is a better approach it's the one I would use.



回答2:

I'll try to explain how I intended to implement this in my project. The requirement is similar as yours: Users have Roles which have Permissions and everything can change from Permission definition, Role's Permission list, and User's Role list etc. So in one moment it's possible that User has access to something and in another, if Administrator alter something, he does not have access.

Before I put some code, I'll answer to your questions.

Do I need to create separate, table-based security?

-Yes

Can I put the security in my repository, so that the returned records are already trimmed, or should it be part of the controller?

-I think security should be a part of business logic so I would put it somewhere in between controller and repository.

Do I need a security attribute to validate the controller request?

-In my project, I've put it in attribute, but sometimes i need to access it from controller to, but since that I keep security logic in business layer, I don't think it is a problem.

First attribute is simple attribute that just allows logged users to execute action:

public class LoggedUserFilterAttribute : ActionFilterAttribute
{
    public bool Logged { get; set; }
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        if (!SessionManager.IsUserLogged)
        {
            filterContext.Result = new RedirectToRouteResult(GetRedirectToNotLoggedRouteValues());
            this.Logged = false;
        }
        else
            this.Logged = true;
    }

    public RouteValueDictionary GetRedirectToNotAuthorizedRouteValues()
    {
        RouteValueDictionary routeValues = new RouteValueDictionary();
        routeValues.Add("action", "NotAuthorized");
        routeValues.Add("controller", "Authorization");
        return routeValues;
    }
    public RouteValueDictionary GetRedirectToNotLoggedRouteValues()
    {
        RouteValueDictionary routeValues = new RouteValueDictionary();
        routeValues.Add("action", "NotLogged");
        routeValues.Add("controller", "Authorization");
        return routeValues;
    }
}

and then I have, for example, attribute which allows only SuperUsers to access it:

public class SuperUserFilterAttribute : LoggedUserFilterAttribute
{
    public override void OnActionExecuting(ActionExecutingContext filterContext)
    {
        base.OnActionExecuting(filterContext);

        if (Logged)
        {
            MyBaseController controller = filterContext.Controller as MyBaseController;
            if (controller == null)
                throw new Exception("Please use MyBaseController instead of built in Controller");

            User loggedUser = controller.Model.UserBO.GetUserByID(SessionManager.LoggedUser.UserID);

            if(!loggedUser.IsSuperUser)
            {
                filterContext.Result = new RedirectToRouteResult(GetRedirectToNotAuthorizedRouteValues());
            }
        }
    }
}

The MyBaseController is class that inherits Controller and has an instance of Model class which represent container for business objects. In controllers action body, if needed I check users rights on current entity and depending on that I return proper view:

    [LoggedUserFilter]
    public ActionResult LoadSomeEntity(int customerServiceID,int entityID)
    {
        UserRights userPermissionsView = Model.SecurityBO.GetUsersRightsOnEntity(SessionManager.LoggedUser.UserID, entityID);

        if(userPermissionsView.Write) 
            return View("EditEntity",Model.EntityBO.GetEntityByID(entityID));
        if(userPermissionsView.Read) 
            return View("ViewEntity",Model.EntityBO.GetEntityByID(entityID));

        return View("NotAuthorized");     
    }

p.s. I'm not sure if I can suggest anything to someone that obviously has much more experience that me :), so if I'm spamming, I apologize for that.