2010-05-13 6 views
25

ASP.NET MVC имеет хорошую поддержку для обеспечения безопасности на основе ролей, но использование строк в качестве имен ролей является безумным, просто потому, что они не могут быть строго типизированы как перечисления.Нестроковые имена ролей в ASP.NET MVC?

Например, у меня есть роль «Админ» в моем приложении. Строка «Admin» теперь будет существовать в атрибуте Authorize моего действия, на моей главной странице (для скрытия вкладки), в моей базе данных (для определения ролей, доступных каждому пользователю) и в любом другом месте в моем коде или представлении файлы, где мне нужно выполнить специальную логику для пользователей admin или non-admin.

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

ответ

19

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

static class Role { 
    public const string Admin = "Admin"; 
} 
+0

Я пошел с этим решением из-за его простоты. Изменения кода были минимальными, так как мне приходилось заменять жестко закодированные строки постоянными ссылками. – MikeWyatt

3

Это не что трудно настроить AuthorizeAttribute так, как вы предлагаете.

Подтипируйте его, добавьте нестандартное свойство для вашего типа перечисления и вызовите ToString() на переданное значение. Поместите это в свойство регулярных ролей. Это займет всего несколько строк кода, а AuthorizeAttribute все еще выполняет всю настоящую работу.

+1 для Матти тоже, поскольку consts также являются хорошим выбором.

2

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

public static class EnumerationExtension 
{ 
    public static string GetName(this Enum e) 
    { 
    return Enum.GetName(e.GetType(), e); 
    } 
} 
46

Использование магических строк дает вам возможность объявить несколько ролей в атрибуте Authorize (например, [Авторизовать (Roles = «Admin, Moderator»)], которые вы, как правило, теряют, как вы идете строго типизированным решение. Но вот как вы можете сохранить эту гибкость в то же время получать все сильно типизированных

Определите свои роли в перечислении, который использует битовые флаги:.

[Flags] 
public enum AppRole { 
    Admin = 1, 
    Moderator = 2, 
    Editor = 4, 
    Contributor = 8, 
    User = 16 
} 

Override 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, или редактор). Поведение такое же, как и атрибут 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(); 
} 
+0

Мне очень нравится этот подход, его легко реализовать и поддерживать – Sam

+0

@Jammer, значения перечисления необязательно должны совпадать с идентификаторами базы данных. Они могут быть независимыми и по-прежнему работать так же хорошо. –

+0

Да, я понял, что так же, как я начал использовать его. Silly me ... – Jammer

10

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

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)

+0

Как бы вы это настроили, сделав его функцией или делегатом? то есть user => user.Role == AssistantRole || user.Role == BigGuy ... Некоторым действиям может понадобиться одна роль, а не другая, некоторым может потребоваться 2 роли или 3-я роль, я надеюсь, что я понятен ??? :) – Haroon

3

Я принял ответ 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(); 
} 
Смежные вопросы