上面一篇文章我们简单介绍了一个一级菜单的应用。
在实际的设计中菜单的的信息基本存储在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,它是我们整个系统的权限核心。
其中对当前用户检查某个规则的有效性代码如下。
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 CustomerMaster.Web.Common.Security
@using 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)用于在页面上显示按钮。
以下核心代码将权限规则和按钮的显示关联。
private static string Button(this HtmlHelper helper, string name, string buttonText, bool disabled, IEnumerable<KeyValuePair<string ,object >> htmlAttributes)
{
HtmlGenericControl a = new HtmlGenericControl("input");
a.ID = name;
a.Attributes["name"] = name;
a.Attributes["value"] = buttonText; a.Attributes["type"] = "button";
if (disabled) a.Attributes["disabled"] = "disabled";
if (htmlAttributes != null)
foreach (KeyValuePair<string, object> attribute in htmlAttributes)
{
a.Attributes[attribute.Key] = attribute.Value.ToString();
}
StringBuilder htmlBuilder = new StringBuilder();
HtmlTextWriter htmlWriter = new HtmlTextWriter(new StringWriter(htmlBuilder));
string html = htmlBuilder.ToString();
return html;
}
在页面中,我们如何利用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 > </span>"+
@Html.Button("toolbarAddRow", UI_Resources.ListCustomer_AddNewCustomerButton, ButtonHelper.SetButtonDisability("toolbarAddRow", "IsAdministrator"), new { title = UI_Resources.ListCustomer_AddNewCustomerButtonTooltip, @class = "icon add" })
+"<span > </span>"+
@Html.Button("toolbarDeleteRow", UI_Resources.ListCustomer_DeleteCustomerButton, ButtonHelper.SetButtonDisability("toolbarDeleteRow", "IsAdministrator"), new { title = UI_Resources.ListCustomer_DeleteCustomerButtonTooltip, @class = "icon delete" })
))
...
显示按钮的时候,我们调用了ButtonHelper.SetButtonDisability来控制按钮的enable/disable状态,我们也可以通过它来控制显示、不显示按钮。
MVC Controller类的访问控制
有些用户可能会直接在浏览器中输入URL来绕过菜单的权限控制,我们必须在MVC的Controller级别加上我们的基于规则的权限管理。
我们增加一个新的类RuleAuthorizeAttribute,它继承于System.Web.Mvc.AuthorizeAttribute, 它利用了SecurityHelper的功能来实现安全审核,因为版权问题具体代码略,读者可以Google相关的代码。
我们把这个属性设置的示例程序中的CustomerController类中。
[HandleError]
[RuleAuthorize(Allow="IsDataSteward")]
public class CustomerController : BaseController
{
}
假设我们登录的用户没有DataSteward或Administrator角色,但是他尝试直接在浏览器里面输入URL:http://localhost:2967/Customer。
新增的Filter控制了直接URL的权限管理。
按钮显示的控制
设计特点
1)架构基于规则,配置简单,省略了繁琐的数据库的表的设计 (用户角色等信息可以用Membership来实现)
2)规则可以存储于数据库中,在整个系统初始化时导入缓存,后续的用户权限管理完全脱离数据库运行,提高了系统整体性能。
3)规则可以适应于菜单项,页面控件,业务逻辑组件,实现了系统各个级别和层次的权限管理,开发编码遵循统一模式
4)实际应用中可以增加页面维护规则和角色的对应关系,这样角色就不受限制了,可以针对具有相同权限控制的菜单项或者控件组预先定义单项访问规则,
系统只需要具有增加新规则和维护该规则对应的角色组的功能即可实现动态控制。