在 MVC 中处理多个角色 - 基于操作的辅助功能

发布于 2024-10-27 23:08:20 字数 962 浏览 1 评论 0原文

我目前有一个项目,我似乎遇到了有关角色的问题,我想我会得到一些关于如何最好地处理该问题的意见。

系统将需要可编辑、灵活的角色,不仅控制特定区域的访问,还控制系统功能的使用(添加用户、编辑用户、查看报告等)。

系统目前允许用户拥有多个角色,每个角色角色具有明确定义的访问/操作区域,例如:

  • 角色 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. 最好的存储方法是什么,以便可以在视图中轻松访问字典? (因为我目前看不到包装我的客户端功能的方法)

任何建议或想法将不胜感激!

I currently have a project that I seem to have ran into an issue regarding Roles and thought I would get some opinions on how to best handle the problem.

The system will require editable, flexible roles that control not only the access of specific areas, but also the use of system functions (Adding Users, Editing Users, Viewing Reports etc.)

The system currently allows users to have multiple roles, each of those roles has explicitly defined areas of access/actions, for example:

  • Role A can access areas 1,2,3 and can Add Users.
  • Role B can access areas 1,5,7 and can Modify Users.
  • Role C can access areas 4,6 and only View Users.

so a User could be in Roles A and C, and thus access : 1,2,3,4 and 6, and could Add and View Users.

My first solution was to create a dictionary that would store all of the possible areas of access/access options into a Dictionary like so:

Dictionary<string,bool>

then when it is instantiated it pulls all of the properties from the database and then iterates through the roles to determine if they are accessible.

All of that currently works just fine - however the project is quite Javascript/jQuery intensive so many of these options are called by client-side functions. I am trying to avoid having to wrap all of these client side functions with:

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

So basically, I am wondering about the following things:

  1. After a user logs in, what is the best way to store this Dictionary? Statically? In the Session?
  2. What would be the best method of storage such that the Dictionary will be easily accessed in the View? (As I currently see no way around wrapping my client-side functions)

Any advice or ideas would be greatly appreciated!

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(1

小…红帽 2024-11-03 23:08:20

我会将此信息存储在身份验证 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'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");

有了这些信息,您现在可以开始考虑如何组织视图模型。在这些视图模型中,我将包含布尔属性,例如 CanEditCanViewReport...,这些属性将由控制器填充。

现在,如果您在每个操作和视图中都需要这种映射,事情可能会变得重复和无聊。这就是全局自定义操作过滤器发挥作用的地方(它们实际上并不存在于 ASP.NET MVC 2 中,仅存在于 ASP.NET MVC 3 中,因此您可能需要一个用此操作过滤器装饰的基本控制器,它模拟或多或少相同的内容功能)。您只需定义这样的全局操作过滤器,该过滤器在每个操作之后执行并向 ViewData 注入一些通用视图模型(天哪......,不敢相信我正在发音这些词),从而使其可用于横向的所有视图其他动作方式。

最后,在视图中,您将检查这些布尔值属性,以便包含或不包含站点的不同区域。就 javascript 代码而言,如果它不显眼地 AJAX 化了站点的区域,那么如果这些区域不存在于 DOM 中,则该代码将不会运行。如果您需要更细粒度的控制,您始终可以在 DOM 元素上使用 HTML5 data-* 属性,以向外部 javascript 函数提供有关用户授权的提示。

I would store this information in the user data part of the authentication cookie. So when a user logs in:

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");
}

Then I would write a custom authroize attribute which will take care of reading and parsing the authentication ticket and store a generic user in the HttpContext.User property with its corresponding roles:

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

Next you could decorate your controllers/actions with this attribute to handle authorization:

// 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()
{
    ...
}

In order to check whether a user is in a given role simply:

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

Armed with this information you can now start thinking of how to organize your view models. In those view models I would include boolean properties such as CanEdit, CanViewReport, ... which will be populated by the controller.

Now if you need this mapping in each action and views things might get repetitive and boring. This is where global custom action filters come into play (they don't really exist in ASP.NET MVC 2, only in ASP.NET MVC 3 so you might need a base controller decorated with this action filter which simulates more or less the same functionality). You simply define such global action filter which executes after each action and injects some common view model to the ViewData (holy ...., can't believe I am pronouncing those words) and thus make it available to all views in a transverse of the other actions manner.

And finally in the view you would check those boolean value properties in order to include or not different areas of the site. As far as the javascript code is concerned if it is unobtrusively AJAXifying areas of the site then if those areas are not present in the DOM then this code won't run. And if you needed more fine grained control you could always use HTML5 data-* attributes on your DOM elements to give hints to your external javascript functions on the authorizations of the user.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文