Нестроковые имена ролей в ASP.NET MVC? - PullRequest
26 голосов
/ 13 мая 2010

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

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

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

Ответы [ 6 ]

48 голосов
/ 21 марта 2011

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

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

[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();
}

Вышеуказанное действие авторизует только тех пользователей, которые хотя бы в одной из перечисленных ролей (Администратор, Модератор или Редактор). Поведение такое же, как и для 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();
}
21 голосов
/ 13 мая 2010

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

static class Role {
    public const string Admin = "Admin";
}
10 голосов
/ 20 мая 2011

Хотя он не использует перечисления, я использовал приведенное ниже решение, где мы подклассифицируем фильтр 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 / магические строки в asp-net-mvc-authorize- фильтры )

3 голосов
/ 19 июля 2012

Я принял ответ 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();
}
3 голосов
/ 13 мая 2010

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

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

+ 1 для Матти тоже, так как конц также хороший выбор.

2 голосов
/ 13 мая 2010

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

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