2011-03-29 45 views
11

我目前有一个项目,我似乎遇到了有关角色的问题,并认为我会就如何最好地处理该问题提出一些意见。在MVC中处理多个角色 - 基于操作的可访问性

系统将要求不仅控制特定区域的接入,而且还使用的系统功能(添加用户,修改用户信息,查看报表等)可编辑的,灵活的身份

目前,该系统允许用户有多个角色,每个角色这些角色已经明确定义的访问/动作的区域,例如:

  • 角色定位可以访问区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. 什么是最好的存储方法,以便在视图中很容易地访问字典? (正如我目前看不到绕过我的客户端功能)

任何意见或想法将不胜感激!

+0

无法通过web.config中的基于位置的授权安全设置来控制此功能吗? – Holystream 2011-03-29 17:58:44

回答

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

然后我会写一个会照顾读取和解析的身份验证票证和存储的HttpContext一个通用的用户自定义属性authroize。用户属性与它对应的角色:

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代码而言,如果它不引人注意,那么如果这些区域不存在于DOM中,那么这些代码将不会运行。如果你需要更细粒度的控制,你总是可以在你的DOM元素上使用HTML5 data-*属性来给你的外部javascript函数提供关于用户授权的提示。

+0

感谢像往常一样非常深入的答案Darin。我会研究整合这样的东西!另外 - 我会给你另一个+1的“神圣......”ViewData提及。 :) – 2011-03-29 20:59:38

+0

如果您要在cookie中存储敏感信息(如用户角色),请确保您拥有所有最新的.NET修补程序。 FormsAuthentication.Encypt()中曾经存在漏洞。 – Ryan 2011-03-31 03:16:16

+0

另外,我非常喜欢数据*的想法。 – Ryan 2011-03-31 03:16:45