上面一篇文章我們簡單介紹了一個一級菜單的應用。
在實際的設計中菜單的的信息基本存儲在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)實際應用中可以增加頁面維護規則和角色的對應關系,這樣角色就不受限制了,可以針對具有相同權限控制的菜單項或者控件組預先定義單項訪問規則,
系統只需要具有增加新規則和維護該規則對應的角色組的功能即可實現動態控制。