Подтвердить что ты не робот

Нестроковые имена ролей в ASP.NET MVC?

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

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

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

4b9b3361

Ответ 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);
  }
}