Нестроковые имена ролей в ASP.NET MVC?
ASP.NET MVC имеет хорошую поддержку для обеспечения безопасности на основе ролей, но использование строк в качестве имен ролей является безумным, просто потому, что они не могут быть строго типизированы как перечисления.
Например, у меня есть роль "Admin" в моем приложении. Строка "Admin" теперь будет существовать в атрибуте Authorize моего действия, на моей главной странице (для скрытия вкладки), в моей базе данных (для определения ролей, доступных каждому пользователю) и в любом другом месте в моем коде или представлении файлы, где мне нужно выполнить специальную логику для пользователей admin или non-admin.
Есть ли лучшее решение, помимо написания моего собственного атрибута авторизации и фильтра, который, возможно, будет иметь дело с набором значений перечисления?
Ответы
Ответ 1
Обычно я использую класс с кучей строковых констант. Это не идеальное решение, так как вам нужно помнить о том, чтобы использовать его везде, но по крайней мере он избавляется от возможности опечаток.
static class Role {
public const string Admin = "Admin";
}
Ответ 2
Использование магических строк дает вам гибкость для объявления нескольких ролей в атрибуте Authorize (например, [Authorize (Роли = "Администратор, Модератор" )], которые вы склонны потерять, когда вы переходите к строго типизированному решению. Но вот как вы может поддерживать эту гибкость, сохраняя при этом все строго типизированное.
Определите свои роли в перечислении, использующем битовые флаги:
[Flags]
public enum AppRole {
Admin = 1,
Moderator = 2,
Editor = 4,
Contributor = 8,
User = 16
}
Переопределить AuthorizeAttribute:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class MyAuthorizeAttribute : AuthorizeAttribute {
public AppRole AppRole { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext) {
if (AppRole != 0)
Roles = AppRole.ToString();
base.OnAuthorization(filterContext);
}
}
Теперь, если вы можете использовать MyAuthorizeAttribute следующим образом:
[MyAuthorize(AppRole = AppRole.Admin | AppRole.Moderator | AppRole.Editor)]
public ActionResult Index() {
return View();
}
Вышеуказанное действие разрешает только пользователям, которые входят, по крайней мере, в одну из перечисленных ролей (Admin, Moderator или Editor). Поведение такое же, как и атрибут AuthorizeAttribute по умолчанию MVC, за исключением без магических строк.
Если вы используете эту технику, здесь может быть полезен метод расширения на IPrincipal:
public static class PrincipalExtensions {
public static bool IsInRole(this IPrincipal user, AppRole appRole) {
var roles = appRole.ToString().Split(',').Select(x => x.Trim());
foreach (var role in roles) {
if (user.IsInRole(role))
return true;
}
return false;
}
}
Вы можете использовать этот метод расширения следующим образом:
public ActionResult Index() {
var allowed = User.IsInRole(AppRole.Admin | AppRole.Moderator | AppRole.Editor);
if (!allowed) {
// Do Something
}
return View();
}
Ответ 3
Хотя он не использует перечисления, я использовал приведенное ниже решение, где мы подклассифицируем фильтр авторизации, чтобы принимать аргументы имени переменной длины в конструкторе. Используя это вместе с именами роли, объявленными в переменных const, мы избегаем магических строк:
public class AuthorizeRolesAttribute : AuthorizeAttribute
{
public AuthorizeRolesAttribute(params string[] roles) : base()
{
Roles = string.Join(",", roles);
}
}
public class MyController : Controller
{
private const string AdministratorRole = "Administrator";
private const string AssistantRole = "Assistant";
[AuthorizeRoles(AdministratorRole, AssistantRole)]
public ActionResult AdminOrAssistant()
{
return View();
}
}
(я писал об этом чуть подробнее - http://tech-journals.com/jonow/2011/05/19/avoiding-magic-strings-in-asp-net-mvc-authorize-filters)
Ответ 4
Не так сложно настроить AuthorizeAttribute
так, как вы предлагаете.
Подтипируйте его, добавьте настраиваемое свойство для вашего типа перечисления и вызовите ToString()
для переданного значения. Поместите это в свойство регулярных ролей. Это займет всего несколько строк кода, а AuthorizeAttribute
все еще выполняет всю реальную работу.
+1 для Матти тоже, поскольку consts также являются хорошим выбором.
Ответ 5
Я взял ответ JohnnyO, но изменил элементы перечисления, чтобы использовать DescriptionAttribute
, чтобы указать строковое значение для этой роли. Это полезно, если вы хотите, чтобы ваша строка ролей отличалась от имени Enum.
Пример перечисления:
[Flags]
public enum AppRole
{
[Description("myRole_1")]
RoleOne = 1,
[Description("myRole_2")]
RoleTwo = 2
}
Метод расширения:
public static bool IsInRole(this IPrincipal user, AppRole appRole)
{
var roles = new List<string>();
foreach (var role in (AppRole[])Enum.GetValues(typeof(AppRole)))
if ((appRole & role) != 0)
roles.Add(role.ToDescription());
return roles.Any(user.IsInRole);
}
Пользовательский атрибут:
[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method)]
public class AppAuthorizeAttribute : AuthorizeAttribute
{
public AppRole AppRoles { get; set; }
public override void OnAuthorization(AuthorizationContext filterContext)
{
var roles = new List<string>();
foreach (var role in (AppRole[])Enum.GetValues(typeof(AppRole)))
if((AppRoles & role) != 0)
roles.Add(role.ToDescription());
if (roles.Count > 0)
Roles = string.Join(",", roles);
base.OnAuthorization(filterContext);
}
}
Метод расширения для получения значения описания:
public static string ToDescription(this Enum value)
{
var da = (DescriptionAttribute[])
(value.GetType().GetField(value.ToString()))
.GetCustomAttributes(typeof (DescriptionAttribute), false);
return da.Length > 0 ? da[0].Description : value.ToString();
}
Ответ 6
Я использовал статический класс, определяющий связку строковых констант, как это предложил Матти, и в моем текущем проекте я использую следующий метод расширения с перечислением. Оба подхода работают очень хорошо.
public static class EnumerationExtension
{
public static string GetName(this Enum e)
{
return Enum.GetName(e.GetType(), e);
}
}