Обработка нескольких ролей в MVC - доступность на основе действий - PullRequest
11 голосов
/ 29 марта 2011

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

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

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

  • Роль 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. Каков наилучший способ сохранить этот словарь после входа пользователя в систему? Статически? На сессии?
  2. Каков наилучший способ хранения, чтобы словарь был легко доступен в представлении? (Поскольку в настоящее время я не вижу возможности обернуть свои функции на стороне клиента)

Буду очень признателен за любые советы или идеи!

1 Ответ

20 голосов
/ 29 марта 2011

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

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's 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, если он незаметно использует AJAXифицирующие области сайта, то если эти области отсутствуют в DOM, этот код не будет работать. А если вам требуется более детальный контроль, вы всегда можете использовать атрибуты HTML5 data-* в ваших элементах DOM, чтобы давать подсказки вашим внешним функциям javascript при авторизации пользователя.

...