zoukankan      html  css  js  c++  java
  • 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;
    分类: 架构设计
  • 相关阅读:
    国内网页登录onedrive
    MySQL查询语法
    python 优矿自动化交易
    Python爬取代理ip
    Python获取中国证券报最新资讯
    python推荐淘宝物美价廉商品
    python PIL比较图片像素
    使用pyinstaller打包Python应用,生成EXE执行文件
    Python可视化----------matplotlib.pylot
    panda库------对数据进行操作---合并,转换,拼接
  • 原文地址:https://www.cnblogs.com/Leo_wl/p/2325318.html
Copyright © 2011-2022 走看看