Защита пользовательских страниц с помощью MVC 4

У меня есть система, в которой все страницы (представления) и все элементы управления (кнопки, ссылки, меню и т.д.) имеют к ним роли безопасности.

Итак, у меня есть интерфейс администратора, где все страницы и элементы управления зарегистрированы. И каждый пользователь имеет набор индивидуальных разрешений.

Итак, например:

У меня есть View EditCar, с 3 кнопками: "Новый", "Удалить" и "Назад".

Таким образом, пользователь X имеет разрешение на просмотр View EditCar, и только кнопка "Назад"

Итак, каждое новое представление должно быть зарегистрировано и ассоциироваться с пользователями. Ролей нет, поскольку каждый пользователь настраивается на 100%.

Итак, у меня есть FilterAttribute:

public class CustomAuthorize : FilterAttribute, IAuthorizationFilter
{
    public void OnAuthorization(AuthorizationContext filterContext)
    {
        if (filterContext.HttpContext.Request.IsAuthenticated)
        {

            var userPermissions = repository.GetAll().Where(x => x.Name.Equals(User.Identity.Name);                

            //   if (!userPermissions.Pages.Any(x => x.NamePage.Contains(???))))               
        }
        else
        {
            filterContext.Result = new HttpUnauthorizedResult();          
        }
    }
}

Итак, мой вопрос: - Что следует хранить в базе данных для определения каждого вида (действия)? Может быть, 3 значения? Area-Controller-Action?

Это лучший вариант? Любая другая идея об этом решении?

Спасибо

Ответ 1

У меня такой же сценарий в моем веб-приложении, и он работает следующим образом:

у нас есть в базе данных:

Разрешение содержит просмотр, добавление, изменение, удаление

Функция содержит всю функцию, которая может быть установлена ​​над ролью

FeaturePermission связывает эту функцию с разрешением, например, какая функция имеет то, что разрешает

UserRole имеет роль пользователя

RoleFeaturePermission показывает, какая роль имеет разрешение на разрешение

Теперь, когда код выполняется, когда пользователь аутентифицируется, я генерирую список назначенных ему полномочий с функциями, тогда я определил Enum like:

public enum FeatureValue
{
    Custom = 1,
    Schedule = 2,
    Export=3          
}

public enum PermissionValue
{
    View = 1,
    Add = 2,
    Edit = 3,
    Delete = 4
}

и статический класс UserPermission для авторизации:

  public static bool VerifyPermission(FeatureValue feature, PermissionValue permission, int id) {
      return getFeaturePermissionsForReport(feature, permission, id);
  }


  private static bool getFeaturePermissionsForReport(FeatureValue feature, PermissionValue permission, int id) {
      SessionHelper sessionHelper = new SessionHelper(null);
      UserModel userModel = sessionHelper .getUser()//get user from session.

      if (userModel != null && userModel.IsAuthorized == false) return false;

      UserProfile userProfile = sessionHelper.Get<UserProfile> ();

      if (userProfile != null && userProfile.AssignedRoleList != null) {
          List<Core.Entities.FeaturePermission> featurePermission = userProfile.AssignedRoleList.SelectMany(b => b.RoleFeaturePermission).ToList();


          if (featurePermission != null) {
              if (featurePermission.Count(f = > f.Feature.Id == (int) feature && f.Permission.Id == (int) permission) > 0) {
                  bool isAllowed= false;

                  int featurePermissionId = featurePermission.Where(f = > f.Feature.Id == (int) feature && f.Permission.Id == (int) permission).Select(i = > i.Id).FirstOrDefault();
                  isAllowed = (reports.Count(r = > (r.FeaturePermissionId == featurePermissionId && r.Id == id)) > 0) ? true : false;

                  return isAllowed;
              }
          }
      }

      return false;
  }

и теперь каждая ссылка, кнопка или действие используют:

 @if (UserPermission.VerifyPermission(FeatureValue.Custom, PermissionValue.Edit))
 {
    //action  link to edit custom view
 }

а для пользовательского атрибута действия:

  [AttributeUsage(AttributeTargets.All,AllowMultiple=true)]
    public class CustomFeaturePermissionAttribute : ActionFilterAttribute
    {
        private FeatureValue[] feature;
        private PermissionValue[] permission;
        private bool excludeParamId;
        /// <summary>
        /// Set values of featurelist and permission list
        /// </summary>
        /// <param name="featureList"></param>
        /// <param name="permissionList"></param>
        public CustomFeaturePermissionAttribute(object featureList,object permissionList, int excludeParamId)
        {
            FeatureList = (FeatureValue[])featureList;
            PermissionList = (PermissionValue[])permissionList;
            ExcludeParamId = excludeParamId;
        }
        public FeatureValue[] FeatureList
        {
            get
            {
                return feature;
            }
            set
            {
                feature = value;
            }
        }

        public bool ExcludeParamId
        {
            get
            {
                return excludeParamId;
            }
            set
            {
                excludeParamId = value;
            }
        }

        public PermissionValue[] PermissionList
        {
            get
            {
                return permission;
            }
            set
            {
                permission = value;
            }
        }
        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            bool isAccessAllowed = false;
            FeatureValue feature;
            PermissionValue permission;

            for (int i = 0; i < FeatureList.Count(); i++)
            {
                feature = FeatureList[i];
                permission = PermissionList[i];

                    isAccessAllowed = UserPermission.VerifyPermission(feature, permission, Convert.ToInt16(ExcludeParamId));

                if (isAccessAllowed)
                    break;
            }

            if (!isAccessAllowed)
            {
                filterContext.Result = new RedirectToRouteResult(new RouteValueDictionary(new { action = "UnauthorizedAccess", controller = "Security" }));
            } 

        }
    }

и в действиях разрешить роль, имеющую разрешение просмотра по умолчанию и экспорту:

[CustomFeaturePermission(new FeatureValue[] { FeatureValue.Custom, FeatureValue.Export }, new PermissionValue[] { PermissionValue.View, PermissionValue.View},pageId)]
public ActionResult Custom()
{
   //action body
}

Ответ 2

Я бы создал абстрактный способ определения каждого разрешения, например перечисления. Например:

public enum UserPermissions
{
    ViewCars,
    EditCars,
    DeleteCars,
    ViewUsers,
    EditUsers,
    DeleteUsers
}

Вы можете создать их в базе данных в таблице под названием "Разрешения", а затем создать сопоставление "многие-ко-многим", где каждому пользователю может быть присвоено любое количество разрешений.

Затем вы должны создать настраиваемый атрибут авторизации путем получения из AuthorizeAttribute и переопределить метод OnAuthorization для загрузки пользователя из базы данных. Это именно то, что вы сделали в своем вопросе, за исключением ключевой части, что вы хотите добавить какое-либо свойство, где вы можете определить разрешения (-и), необходимые для действия, например:

public class UserPermissionsAttribute : AuthorizeAttribute
{
    public IEnumerable<UserPermissions> PermissionsRequired { get; set; }

    public UserPermissionsAttribute()
    {
    }

    public UserPermissionsAttribute(params UserPermissions[] permissionsRequired)
    {
        PermissionsRequired = permissionsRequired;
    }

    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        var user = filterContext.HttpContext.User; // get user from DB

        if (PermissionsRequired.All(x => user.Permissions.Any(y => x == y)))
        {
            // all permissions are met
            base.OnAuthorization(filterContext);
        }
        else
        {
            throw new UnauthorizedAccessException();
        }

        base.OnAuthorization(filterContext);
    }
}

Теперь вы можете украсить каждое действие или контроллер разрешением или списком разрешений:

[UserPermissions(UserPermissions.ViewCars, UserPermissions.EditCars)]
public ActionResult Index()
{
    ViewBag.Title = "Home Page";

    return View();
}

Таким образом вы отделяете свою систему разрешений от логики контроллера/логики MVC.

Хотя я бы советовал против этого метода хранения каждого разрешения на индивидуальной основе. Система ролей упрощает работу и улучшает производительность. Я действительно думаю, что вы могли бы сделать это с помощью нескольких мелкозернистых ролей вместо мелкомасштабных разрешений.

Ответ 3

Обратите внимание, что авторизация пользователей для просмотра определенных элементов страницы отличается от авторизации для CRUD или других операций с базой данных, если только элементы не указывают на операционные действия в контроллере. Учтите, что у вас могут быть некоторые элементы, которые не нужно видеть конкретному пользователю и не имеют конкретной операции с базой данных. До сих пор мы заключаем, что нам нужны следующие разрешения:

  • Разрешение на просмотр
  • Разрешение на команду

Я считаю, что вы можете использовать Поставщик ролей Microsoft для обеих частей. Согласно документации MSDN Учитывая, что:

Атрибут Authorize позволяет указать, что авторизация ограничивается предопределенными ролями или отдельными пользователями. Это дает вам высокая степень контроля над тем, кому разрешено просматривать любую страницу на сайт.

В следующем шаге/вопрос, как это сделать?

Я думаю, что для достижения нашей цели доступны 3 способа:

  • Решение 1: Создание отдельных представлений с конкретными элементами страницы из-за пересылки каждого пользователя в соответствующий вид. В этом сценарии мы должны также создайте отдельные действия контроллера. мы должны проверять типы пользователей перед каждым действием вроде [Authorise(Roles="Administrator")]. Мы вынуждены иметь статические (предварительно определенные) роли и доступность. И в одно предложение Нехорошее решение из-за избыточности и Нестабильность.

  • Решение 2: Создание страниц Динамически просто добавив некоторые условия if для каждого элемента ограниченного доступа в Одна страница (для пример Редактировать страницу). Это похоже на использование @if (User.IsInRole("Admin")) для авторизации определенных пользователей и показ связанные элементы страницы, такие как кнопки. На стороне контроллера мы можем использовать if (не как FilterAttribute из-за добавления динамических функциональность, основанная на сгенерированных/добавленных новых ролях) и правильное управление транзакции против базы данных. Хотя FilterAttribute добавить некоторых замечательных функционалистов (например, оптимизацию производительности). В одном предложении Умеренное решение.

  • Решение 3: Действуйте как решение 2, просто исправьте проблему с контроллером создавая свой собственный фильтр FilterAttribute для авторизации. Что будет унаследованный от AuthorizeAttribute и переопределяет OnAuthorize метод делать то, что вам нужно только для операций.

Пример:

public class TableAuthorizeAttribute : AuthorizeAttribute
{
    public enum TableAction
    {
        Read,
        Create,
        Update,
        Delete
    }
    public TableAction Action { get; set; }
    public override void OnAuthorization(AuthorizationContext filterContext)
    {
        base.OnAuthorization(filterContext);
        //do custom authorizization using Action and getting TableEntryID 
        //from filterContext.HttpContext.Request.QueryString or
        //filterContext.HttpContext.Request.Form
    }
}

И его использование будет таким:

[TableAuthorize(Action=TableAuthorizeAttribute.TableAction.Update)]

Здесь приведен полный пример о концепции выше. Вот полный пример создания динамического AuthorizeAttribute для авторизации новых ролей, добавленных в приложение.

Решение 3 в одном предложении Идеальное, но сложное решение.

Обратите внимание, что с помощью FilterAttribute до Actions мы ограничили наше приложение статическими/предопределенными ролями. Не нужно использовать другую структуру данных или создавать таблицы в базе данных.

Ответ 4

Я видел аналогичную реализацию в прошлом, которая использовала концепцию маркера.

Каждый метод Action представлен токеном. Определите роль маркеров. Роль назначается пользователю.

Я использовал простое консольное приложение, чтобы отразить мое приложение MVC и искать все контроллеры и определять каждый метод действий внутри них.

Храните эти "токены" в своей базе данных вместе с вашими ролями.

Реализация оставалась простой и просто использовала полное имя с пространствами имен и т.д., чтобы идентифицировать их. Таким образом, данные должны быть конкретными для вашего приложения, которые могут повысить безопасность

Ответ 5

Я бы взял подход Тревора, но не использовал бы атрибут. Я бы создал общий разрешающий действие action, например:


[Flags]
internal enum PermissionsEnum
{
    listbutton   = 1,
    editbutton   = 2,
    deletebutton = 4,
    savebutton   = 8,
    createbutton = 16,
    action03 = 32,
    action04 = 64,
    action05 = 128,
    action06 = 256,
    action07 = 512,
    action08 = 1024,
    action09 = 2048,
    action10 = 4096,
    action11 = 8192,
    action12 = 16384,
    action13 = 32768
}

Такой объект разрешения, который я храню для каждой области/контроллера и пользователя в базе данных, например, с некоторыми дополнительными ограничениями значение разрешения -1 не разрешено вызывать действие и значение разрешения 0 для вызова действия, но никаких других разрешений:


Controller/Action   UserId    Permission
=================   ======    =========
cars/delete         User0001  -1 
cars/edit           User0001  8  
cars/index          User0001  0
cars/list           User0001  16
cars/show           User0001  2

Примените разрешения, которые я бы создал базовый контроллер. Когда вызывается какое-либо действие, базовый контроллер получает разрешения для вызываемого контроллера:


var currentController = this.Url.RouteData["controller"];
var currentAction = this.Url.RouteData["action"];
var currentUserPermissons = GetUserPermissonForController(string.Format("{0}/{1}",currentController,currentAction), userId);
if( 0 > currentUserPermissons ) RedirectToAction("PermissonDenied","Error");
ViewBag.UserPermissons = (PermissionsEnum)currentUserPermissons;

В каждом представлении я должен проверить ViewBag.UserPermissons перед созданием защищенного элемента, например:


@{ if((ViewBag.UserPermissons & PermissionsEnum.listbutton) == PermissionsEnum.listbutton)
    {
        @Html.ActionLink("Listitems","List")
    }
}