[转载]Asp.net MVC 基于规则的权限设计

[转载]Asp.net MVC 基于规则的权限设计 – 胡以谦 – 博客园.

上面一篇文章我们简单介绍了一个一级菜单的应用。

在实际的设计中菜单的的信息基本存储在sitemap的xml文件中,菜单还涉及到权限问题。

本章将介绍并举例说明如何设计基于规则的MVC应用程序的安全性。

基于角色的授权

在计算机系统的安全,基于角色的访问控制(RBAC)是一个系统访问限制授权用户的方法。在一个组织内,角色创建的各项工作职能。来执行某些操作的权限分配给特定的角色。
业务上我们必须定义一套针对不同的业务功能的角色体系,例如管理员,数据管理员,普通用户的角色… …

基于规则的访问控制

以规则为基础的授权框架,一般利用XML文档存储简单的规则设置,来控制系统访问权限。(也可以存储在数据库中,读者可以扩展Enterprise Library)

请参见下面的例子。

<rules>
    <add expression="R:Administrator" name="IsAdministrator" />
     <add expression="R:Administrator OR R:DataSteward" name="IsDataSteward" />
     <add expression="R:User OR R:DataSteward OR R:Administrator"  name="IsUser" />
 </rules>

规则“IsAdministrator”会检查当前用户是否有Administrator的角色。 “IsUser”将对角色User, DataSteward或者Administrator都有效。

SecurityHelper

SecurityHelper类利用了Enterprise Library 的默认的AuthorizationRuleProvider,它是我们整个系统的权限核心。主要代码如下。

using System.Collections.Generic;
using Microsoft.Practices.EnterpriseLibrary.Security;
using Microsoft.Practices.Unity;
using Volvo.CustomerMaster.Infrastructure.Common.Utilities.Entities;
using Volvo.CustomerMaster.Infrastructure.Common.Utilities.Unity;
using Volvo.POS.UserDomain.ServiceLayer;
using Volvo.POS.UserDomain.DomainLayer;
using Volvo.CustomerMaster.Web.Common.Session;
namespace Volvo.CustomerMaster.Web.Common.Security
{
public class SecurityHelper
{
[Dependency]
public static IUserService UserService { get; set; }
/// <summary>
/// Authenticate the user to verify that the user is a logged in user and that the user is approved
/// by the external authorization system.
/// </summary>
/// <returns></returns>
public static bool Authenticate()
{
// Inject implementation of the UserService through DI
if (UserService == null)
{
UserService = Container.Resolve<UserService>();
}
string userName = GetWindowsVcnUserName();
// Get user from external authorization system
GenericUser user = UserService.GetUser(userName);
if (user == null)
{
return false;
}
// Set session
SessionWrapper.CurrentUser = user;
return true;
}
/// <summary>
/// Returns true if the user contain roles that is valid for selected rule
/// </summary>
/// <param name="rule"></param>
/// <returns></returns>
public static bool Authorized(string rule)
{
try
{
IList<string> rules = new List<string> { rule };
return Authorized(rules);
}
catch
{
return false;
}
}
/// <summary>
/// Returns true if the user contain roles that is valid for selected rules
/// </summary>
/// <param name="rules"></param>
/// <returns></returns>
public static bool Authorized(IList<string> rules)
{
// If user is not defined, try to authenticate it
if (SessionWrapper.CurrentUser == null)
{
if (!Authenticate())
{
return false;
}
}
// Get authorization provider from Entlib
IAuthorizationProvider auth = AuthorizationFactory.GetAuthorizationProvider("RulesProvider");
if (rules.Count > 0 && SessionWrapper.CurrentUser.Principal != null)
{
foreach (string rule in rules)
{
// Authorize user (with its roles) agains the rule
if (!auth.Authorize(SessionWrapper.CurrentUser.Principal, rule))
{
return false;
}
}
}
else
{
return false;
}
return true;
}
private static string GetWindowsVcnUserName()
{
// Get windows user
System.Security.Principal.WindowsIdentity loggedInUser =
System.Security.Principal.WindowsIdentity.GetCurrent();
if (loggedInUser != null)
{
string username = loggedInUser.Name;
username = username.Substring(username.IndexOf('\\') + 1);
username = username.ToUpper();
return username;
}
return null;
}
}
}

其中对当前用户检查某个规则的有效性代码如下。

IAuthorizationProvider auth = AuthorizationFactory.GetAuthorizationProvider("RulesProvider");
if (rules.Count > 0 && SessionWrapper.CurrentUser.Principal != null)
{
foreach (string rule in rules)
{
// Authorize user (with its roles) agains the rule
if (!auth.Authorize(SessionWrapper.CurrentUser.Principal, rule))
{
return false;
}
}
}
else
{
return false;
}
return true;

菜单的访问控制

在Web.sitemap文件中我们对每个节点增加一个属性,AuthorizationRule这样菜单和用户角色就关联起来了。

<?xml version="1.0" encoding="utf-8" ?>
<siteMap enableLocalization="true">
  <siteMapNode title="Menu">
    <siteMapNode controller="Home" title="Home"    action="Index" resourceKey="Tab_Home" AuthorizationRule="IsUser"/>
    <siteMapNode controller="Customer" title="Manage Customers"    action="Index" resourceKey="Tab_ManageCustomers" AuthorizationRule="IsDataSteward"/>
    <siteMapNode title="Switching Brands" resourceKey="Tab_SwitchingBrands" AuthorizationRule="IsUser">
      <siteMapNode title="Violin" controller="Home" action="SetTheme/Violin"  AuthorizationRule="IsUser"/>
      <siteMapNode title="Mack" controller="Home" action="SetTheme/Mack" AuthorizationRule="IsUser"/>
      <siteMapNode title="Mack Dual" controller="Home" action="SetTheme/MackDual" AuthorizationRule="IsUser"/>
      <siteMapNode title="Renault" controller="Home" action="SetTheme/Renault"  AuthorizationRule="IsUser"/>
      <siteMapNode title="Volvo BA" controller="Home" action="SetTheme/VolvoBA" AuthorizationRule="IsUser"/>
      <siteMapNode title="Volvo Group" controller="Home" action="SetTheme/VolvoGroup" AuthorizationRule="IsUser"/>
    </siteMapNode>
  </siteMapNode>
</siteMap>

菜单的规则如何、什么时候被加载呢?在渲染菜单的SiteMapBinding.cshtml文件中,我们的代码如下。(示例利用了Telerik for ASP.NET MVC控件)

@using Volvo.CustomerMaster.Web.Common.Security
@using Volvo.CustomerMaster.Web

 @{ Html.Telerik().Menu()
        .Name("Menu")
        .BindTo("Web",(item, node) =>{
             if (node.Attributes["resourceKey"] !=null)
                item.Text = UI_Resources.ResourceManager.GetString(node.Attributes["resourceKey"] as string) ?? item.Text;
            if(node.Attributes["imageurl"] != null)
            item.ImageUrl = node.Attributes["imageurl"].ToString();
            item.Visible = SecurityHelper.Authorized(node.Attributes["AuthorizationRule"].ToString());
        })
         .Effects(fx =>
          fx.Toggle()
          .OpenDuration(200)
          .CloseDuration(200))
       .Render();
 }

其中item.Visible=SecurityHelper.Authorized(node.Attributes[“AuthorizationRule”].ToString());这行代码就决定了菜单的可见性由我们定义的规则控制。

UI元素访问控制

利用同样原理,按钮的enable/disable也可以基于规则来控制。我们首先构造一个类 (HtmlHelper)用于在页面上显示按钮。

ButtonHelper

以下核心代码将权限规则和按钮的显示关联。

Button

在页面中,我们如何利用ButtonHelper呢?下面的例子利用Telerik来显示一个Grid,在Grid的头上我么将显示edit, add, delete 按钮。

按钮的生成就利用了我么的ButtonHelper类。它提供了一些扩展方法。

@(Html.Telerik().Grid<Customer>()
         .Name("CustomerGrid")
         .EnableCustomBinding(true)
         .DataBinding(bind => bind.Ajax().Select("ListCustomerAjax", "Customer"))
         .ToolBar(toolBar => toolBar.Template
          (
             @Html.Button("toolbarEditRow", UI_Resources.ListCustomer_EditCustomerButton, 
             ButtonHelper.SetButtonDisability("toolbarEditRow", "IsAdministrator"), 
             new { title = UI_Resources.ListCustomer_EditCustomerButtonTooltip, @class = "icon edit" })
             +"<span >&nbsp;</span>"+
             @Html.Button("toolbarAddRow", UI_Resources.ListCustomer_AddNewCustomerButton, ButtonHelper.SetButtonDisability("toolbarAddRow", "IsAdministrator"), new { title = UI_Resources.ListCustomer_AddNewCustomerButtonTooltip, @class = "icon add" })
             +"<span >&nbsp;</span>"+
             @Html.Button("toolbarDeleteRow", UI_Resources.ListCustomer_DeleteCustomerButton, ButtonHelper.SetButtonDisability("toolbarDeleteRow", "IsAdministrator"), new { title = UI_Resources.ListCustomer_DeleteCustomerButtonTooltip, @class = "icon delete" })
          ))
         .Columns(columns =>
                    {
                        columns.Bound(o => o.Number).Title("Number").Width(40);
                        columns.Bound(o => o.Name).Title("Name").Width(100);
                        columns.Bound(o => o.Address).Title("Address").Width(100);
                    }
                 )
        .ClientEvents(x => x.OnLoad("CustomerGrid_OnLoad"))
        .Selectable()
        .Pageable(paging => paging.PageSize(10).Total(Model.CustomerCount))
        .Sortable())

显示按钮的时候,我们调用了ButtonHelper.SetButtonDisability来控制按钮的enable/disable状态,我们也可以通过它来控制显示、不显示按钮。

MVC Controller类的访问控制

有些用户可能会直接在浏览器中输入URL来绕过菜单的权限控制,我们必须在MVC的Controller级别加上我们的基于规则的权限管理。

我们增加一个新的类RuleAuthorizeAttribute,它继承于System.Web.Mvc.AuthorizeAttribute

View Code

代码很简单,它也利用了我们前面提到的SecurityHelper类的功能。

我们把这个属性设置的示例程序中的CustomerController类中。

    [HandleError]
    [RuleAuthorize(Allow="IsDataSteward")]
    public class CustomerController : BaseController
    {
        public ICustomerService CustomerService { get; set; }

        public CustomerController(ICustomerService customerService)
        {
            CustomerService = customerService;  
            
        }

假设我们登录的用户没有DataSteward或Administrator角色,但是他尝试直接在浏览器里面输入URL:http://localhost:2967/Customer。

新增的Filter控制了直接URL的权限管理。

按钮显示的控制

———————————————————————-
示例代码.

http://files.cnblogs.com/huyq2002/Sample.zip

运行程序您需要生成数据库CustomerMaster,运行CustomerMaster.SQL,同时修改NHibernate.config中的connection.connection_string

系统适用于域认证,也很容易扩展到其他认证方式,如form认证等

通过改变代码中的UserService的GetUser方法可以模拟不同的角色来使用系统

// 2. Get roles defined for the user
            if (userName.Equals("v0cn174", StringComparison.CurrentCultureIgnoreCase))
            {
                //user.AddRole(new UserRole(UserRoleConstants.Administrator));
                user.AddRole(new UserRole(UserRoleConstants.DataSteward));
                //user.AddRole(new UserRole(UserRoleConstants.User));
            }
            else
            {
                // All users are superusers in this mock
                //user.AddRole(new UserRole(UserRoleConstants.Administrator));
                //user.AddRole(new UserRole(UserRoleConstants.DataSteward));
                user.AddRole(new UserRole(UserRoleConstants.User));
            }
            return user;
赞(0) 打赏
分享到: 更多 (0)

觉得文章有用就打赏一下文章作者

支付宝扫一扫打赏

微信扫一扫打赏