Обработка нескольких ролей в MVC - доступность на основе действий

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

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

В настоящее время система позволяет пользователям иметь несколько ролей, каждая из этих ролей имеет явно определенные области доступа/действия, например:

  • Роль A может иметь доступ к областям 1,2,3 и может добавлять пользователей.
  • Роль B может получить доступ к областям 1,5,7 и может изменять пользователей.
  • Роль C может обращаться к областям 4,6 и просматривать только пользователей.

поэтому пользователь может находиться в Ролях A и C и, таким образом, получить доступ: 1,2,3,4 и 6 и может добавлять и просматривать пользователей.

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

Dictionary<string,bool>

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

Все это в настоящее время работает очень хорошо - однако проект довольно интенсивно Javascript/jQuery, поэтому многие из этих параметров вызываются клиентскими функциями. Я стараюсь не включать все эти функции на стороне клиента:

<%if(AccessDictionary[key])
     //Enable or Disable Action
<%}%>

Итак, в основном, мне интересно о следующих вещах:

  • После входа пользователя в систему, что является лучшим способом для хранения этого словаря? Статически? В сеансе?
  • Какой был бы лучший способ хранения, чтобы словарь легко доступен в представлении? (Поскольку я в настоящее время не вижу возможности обойти мои клиентские функции)

Приветствуются любые советы или идеи!

Ответ 1

Я бы сохранил эту информацию в пользовательской части данных cookie аутентификации. Поэтому, когда пользователь входит в систему:

public ActionResult Login(string username, string password)
{
    // TODO: validate username/password couple and 
    // if they are valid get the roles for the user

    var roles = "RoleA|RoleC";
    var ticket = new FormsAuthenticationTicket(
        1, 
        username,
        DateTime.Now, 
        DateTime.Now.AddMilliseconds(FormsAuthentication.Timeout.TotalMilliseconds), 
        false, 
        roles
    );
    var encryptedTicket = FormsAuthentication.Encrypt(ticket);
    var authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, encryptedTicket)
    {
        // IIRC this property is only available in .NET 4.0,
        // so you might need a constant here to match the domain property
        // in the <forms> tag of the web.config
        Domain = FormsAuthentication.CookieDomain,
        HttpOnly = true,
        Secure = FormsAuthentication.RequireSSL,
    };
    Response.AppendCookie(authCookie);
    return RedirectToAction("SomeSecureAction");
}

Затем я напишу специальный атрибут authroize, который позаботится о чтении и анализе билета аутентификации и сохранит общий пользователь в свойстве HttpContext.User с его соответствующими ролями:

public class MyAuthorizeAttribute : AuthorizeAttribute
{
    protected override bool AuthorizeCore(HttpContextBase httpContext)
    {
        if (httpContext.User.Identity.IsAuthenticated)
        {
            var authCookie = httpContext.Request.Cookies[FormsAuthentication.FormsCookieName];
            if (authCookie != null)
            {
                var ticket = FormsAuthentication.Decrypt(authCookie.Value);
                var roles = ticket.UserData.Split('|');
                var identity = new GenericIdentity(ticket.Name);
                httpContext.User = new GenericPrincipal(identity, roles);
            }
        }
        return base.AuthorizeCore(httpContext);
    }
}

Затем вы можете украсить свои контроллеры/действия этим атрибутом для разрешения авторизации:

// Only users that have RoleA or RoleB can access this action
// Note that this works only with OR => that how the base
// authorize attribute is implemented. If you need to handle AND
// you will need to completely short-circuit the base method call
// in your custom authroize attribute and simply handle this
// case manually
[MyAuthorize(Roles = "RoleA,RoleB")]
public ActionResult Foo()
{
    ...
}

Чтобы проверить, является ли пользователь в заданной роли просто:

bool isInRole = User.IsInRole("RoleC");

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

Теперь, если вам нужно это сопоставление в каждом действии, и просмотры могут стать повторяющимися и скучными. Именно здесь вступают в действие глобальные фильтры пользовательских действий (они не существуют в ASP.NET MVC 2, только в ASP.NET MVC 3, поэтому вам может понадобиться базовый контроллер, украшенный этим фильтром действий, который имитирует более или менее одинаковые функциональность). Вы просто определяете такой глобальный фильтр действий, который выполняется после каждого действия и вводит некоторую общую модель представления в ViewData (святой...., не могу поверить, что я произношу эти слова) и таким образом сделать его доступным для всех представлений в поперечном другие действия.

И, наконец, в представлении вы должны проверить эти свойства логического значения, чтобы включить или не использовать разные области сайта. Что касается кода javascript, если это ненавязчиво AJAXifying областей сайта, то если эти области не присутствуют в DOM, этот код не будет работать. И если вам нужен более мелкозернистый элемент управления, вы всегда можете использовать атрибуты HTML5 data-* на своих элементах DOM, чтобы давать подсказки вашим внешним функциям javascript при авторизации пользователя.