好得很程序员自学网

<tfoot draggable='sEl'></tfoot>

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

 public   static   class  ButtonHelper
{
private static IEnumerable<KeyValuePair< string , object >> GetAttributeValues( object attributeValues)
{
if (attributeValues != null )
{
Type type = attributeValues.GetType();
// PropertyInfo[] properties = type.GetProperties();
foreach (PropertyInfo property in type.GetProperties())
{
string attributeName = property.Name;
object value = property.GetValue(attributeValues, null );
yield return new KeyValuePair< string , object >(attributeName, value);
}
}
}


/// <summary>
/// Renders an HTML form submit button including authorization
/// </summary>
public static string Button( this HtmlHelper helper, string name, string buttonText)
{
return Button(helper, name, buttonText, false , null );
}

public static string Button( this HtmlHelper helper, string name, string buttonText, object htmlAttributes)
{
return Button(helper, name, buttonText, false , GetAttributeValues(htmlAttributes));
}

public static string Button( this HtmlHelper helper, string name, string buttonText, bool disabled, object htmlAttributes)
{
return Button(helper, name, buttonText, disabled, GetAttributeValues(htmlAttributes));
}

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));
// render the html
a.RenderControl(htmlWriter);
string html = htmlBuilder.ToString(); // = String.Format("<input type=\"submit\" name=\"{0}\" value=\"{1}\" ", name, buttonText);
// html += (disabled) ? " disabled=\"disabled\" />" : "/>";
return html;
}
/// <summary>
/// Renders an HTML form image button
/// </summary>
public static string AddFilesButton( this HtmlHelper helper, string imageURI)
{
if ( string .IsNullOrEmpty(imageURI))
imageURI = helper.ViewContext.HttpContext.Request.ApplicationPath +
" http://www.cnblogs.com/Content/Images/Volvo Icons/Add Files/Add_16x16.png " ;
return String.Format( " <input type=\"image\" src=\"{0}\" /> " , imageURI);
}

public static bool SetButtonDisability( string id, string rule)
{
try
{
if (SecurityHelper.Authorized(rule))
{
return false ;
}
}
catch
{ }
return true ;
}
}

复制代码

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

Button

 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));
// render the html
a.RenderControl(htmlWriter);
string html = htmlBuilder.ToString(); // = String.Format("<input type=\"submit\" name=\"{0}\" value=\"{1}\" ", name, buttonText);
// html += (disabled) ? " disabled=\"disabled\" />" : "/>";
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 >&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的权限管理。

按钮显示的控制

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

https://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;

分类:  架构设计

作者: Leo_wl

    

出处: http://www.cnblogs.com/Leo_wl/

    

本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利。

版权信息

查看更多关于Asp.net MVC 基于规则的权限设计的详细内容...

  阅读:54次