Защита пользовательских страниц с помощью 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")
}
}