基於MVC4+EasyUI的Web開發框架造成之旅--權限控制

我在上一篇隨筆《基於MVC4+EasyUI的Web開發框架造成之旅--框架整體界面介紹》中大概介紹了基於MVC的Web開發框架的權限控制整體思路。其中的權限控制就是分爲「用戶登陸身份驗證」、「控制器方法權限控制」、「界面元素權限控制」三種控制方式,能夠爲Web開發框架自己提供了很好用戶訪問控制和權限控制,使得用戶界面呈現菜單、Web界面的按鈕和內容、Action的提交控制,均能在整體權限功能分配和控制之下。javascript

本篇文章主要細化這三個方面的介紹,重點介紹「控制器方法權限控制」、「界面元素權限控制」這兩種權限控制方式。html

 一、用戶登陸控制

登陸界面以下所示。前端

其中登陸的前臺頁面代碼以下所示,其中能夠在登陸界面接收驗證碼(若是必要的話)。java

        //實現用戶登陸
        function LoginUserInfo() {
            //獲取單擊用戶登陸按鈕的事件
            $("#btnLogin").click(function () {
                //首先獲取到要傳遞到控制器的參數,而且狗形成Json。UserName,UserPassword,Code
                var postData = {
                    UserName: $("#UserName").val(),
                    Password: $("#Password").val(),
                    Code: $("#Code").val()
                };

                //發送異步請求實現登陸 ajax
                $.ajax({
                    url: '/Login/CheckUser',
                    data: postData,
                    cache: false,
                    async: true,
                    type: 'post',
                    success: function (data) {
                        if (data == "OK") {
                            window.location.href = "/Home/Index";

                        } else {
                            alert(data);
                            window.location.href = "/Login/Index";
                        }
                    }
                });
            });
        }

用戶登陸的後臺控制器方法以下所示:ajax

        /// <summary>
        /// 對用戶登陸的操做進行驗證
        /// </summary>
        /// <param name="username">用戶帳號</param>
        /// <param name="password">用戶密碼</param>
        /// <param name="code">驗證碼</param>
        /// <returns></returns>
        public ActionResult CheckUser(string username, string password, string code)
        {
            string result = "";

            bool codeValidated = true;
            if (this.TempData["ValidateCode"] != null)
            {
                codeValidated = (this.TempData["ValidateCode"].ToString() == code);
            }

            if (string.IsNullOrEmpty(username))
            {
                result = "用戶名不能爲空";
            }
            else if (!codeValidated)
            {
                result = "驗證碼輸入有誤";
            }
            else
            {
                string ip = GetClientIp();
                string macAddr = "";
                string identity = BLLFactory<WHC.Security.BLL.User>.Instance.VerifyUser(username, password, MyConstants.SystemType, ip, macAddr);
                if (!string.IsNullOrEmpty(identity))
                {
                    UserInfo info = BLLFactory<WHC.Security.BLL.User>.Instance.GetUserByName(username);
                    if (info != null)
                    {
                        result = "OK";
                        Session["UserInfo"] = info;
                        Session["Identity"] = info.Name.Trim();

                        #region 取得用戶的受權信息,並存儲在Session中

                        List<FunctionInfo> functionList = BLLFactory<Function>.Instance.GetFunctionsByUser(info.ID, MyConstants.SystemType);
                        Dictionary<string, string> functionDict = new Dictionary<string, string>();
                        foreach (FunctionInfo functionInfo in functionList)
                        {
                            if (!string.IsNullOrEmpty(functionInfo.ControlID) &&
                                !functionDict.ContainsKey(functionInfo.ControlID))
                            {
                                functionDict.Add(functionInfo.ControlID, functionInfo.ControlID);
                            }
                        }
                        Session["Functions"] = functionDict;

                        #endregion
                    }
                }
                else
                {
                    result = "用戶名輸入錯誤或者您已經被禁用";
                }
            }

            return Content(result);
        }

從上面的代碼,咱們能夠看到,在用戶登陸成功後,後臺把用戶信息、用戶權限列表信息放到了Session裏面,方便進行後面的權限控制。數據庫

而後當前端頁面得到成功響應並切換到Home的Index視圖前,後臺會調用Home的控制器,把一些用戶信息放到了ViewBag對象裏面,並構造用戶的相關菜單項目,代碼以下所示。框架

    public class HomeController : BaseController
    {
        public ActionResult Index()
        {
            if (CurrentUser != null)
            {
                ViewBag.FullName = CurrentUser.FullName;
                ViewBag.Name = CurrentUser.Name;

                StringBuilder sb = new StringBuilder();
                List<MenuInfo> menuList = BLLFactory<Menu>.Instance.GetTopMenu(MyConstants.SystemType);
                int i = 0;
                foreach (MenuInfo menuInfo in menuList)
                {
                    sb.Append(GetMenuItemString(menuInfo, i));
                    i++;
                }
                ViewBag.HeaderScript = sb.ToString();//一級菜單代碼
            }
            return View();            
        }

 

二、控制器方法權限控制

 咱們知道,對頁面的權限控制,能夠分爲前端控制和後臺代碼的控制,控制器方法的權限控制屬於後臺代碼的控制。爲了方便基類代碼的權限控制,咱們定義一個權限控制鍵的類,用來記錄通用的增長、修改、刪除、查看、列表、導出等傳統控制元素,代碼以下所示。異步

    /// <summary>
    /// 定義經常使用功能的控制ID,方便基類控制器對用戶權限的控制
    /// </summary>
    [DataContract]
    [Serializable]
    public class AuthorizeKey
    {
        #region 常規功能控制ID
        /// <summary>
        /// 新增記錄的功能控制ID
        /// </summary>
        public string InsertKey { get; set; }

        /// <summary>
        /// 更新記錄的功能控制ID
        /// </summary>
        public string UpdateKey { get; set; }

        /// <summary>
        /// 刪除記錄的功能控制ID
        /// </summary>
        public string DeleteKey { get; set; }

        /// <summary>
        /// 查看列表的功能控制ID
        /// </summary>
        public string ListKey { get; set; }

        /// <summary>
        /// 查看明細的功能控制ID
        /// </summary>
        public string ViewKey { get; set; }

        /// <summary>
        /// 導出記錄的功能控制ID
        /// </summary>
        public string ExportKey { get; set; } 
        #endregion

        #region 常規權限判斷
        /// <summary>
        /// 判斷是否具備插入權限
        /// </summary>
        public bool CanInsert { get; set; }

        /// <summary>
        /// 判斷是否具備更新權限
        /// </summary>
        public bool CanUpdate { get; set; }

        /// <summary>
        /// 判斷是否具備刪除權限
        /// </summary>
        public bool CanDelete { get; set; }

        /// <summary>
        /// 判斷是否具備列表權限
        /// </summary>
        public bool CanList { get; set; }

        /// <summary>
        /// 判斷是否具備查看權限
        /// </summary>
        public bool CanView { get; set; }

        /// <summary>
        /// 判斷是否具備導出權限
        /// </summary>
        public bool CanExport { get; set; }

        #endregion

        /// <summary>
        /// 默認構造函數
        /// </summary>
        public AuthorizeKey() { }

        /// <summary>
        /// 經常使用構造函數
        /// </summary>
        public AuthorizeKey(string insert, string update, string delete, string view = "") 
        {
            this.InsertKey = insert;
            this.UpdateKey = update;
            this.DeleteKey = delete;
            this.ViewKey = view;
        }
    }

有了這個實體類,咱們就能夠在控制器的基類BaseController裏面實現一些控制邏輯了。首先咱們在控制器每次執行方法前,都對權限進行一個轉換,並把控制鍵存儲到ViewBage裏面,方便前端頁面的控制,以下代碼所示。async

        /// <summary>
        /// 從新基類在Action執行以前的事情
        /// </summary>
        /// <param name="filterContext">重寫方法的參數</param>
        protected override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);

            //獲得用戶登陸的信息
            CurrentUser = Session["UserInfo"] as UserInfo;            
            if (CurrentUser == null)
            {
                Response.Redirect("/Login/Index");//若是用戶爲空跳轉到登陸界面
            }

            //設置受權屬性,而後賦值給ViewBag保存
 ConvertAuthorizedInfo(); ViewBag.AuthorizeKey = AuthorizeKey;
        }

其中ConvertAuthorizedInfo()函數是驗證登錄用戶是否具備相應的權限的。ide

        /// <summary>
        /// 對AuthorizeKey對象裏面的操做權限進行賦值,用於頁面判斷
        /// </summary>
        protected virtual void ConvertAuthorizedInfo()
        {
            //判斷用戶權限
            AuthorizeKey.CanInsert = HasFunction(AuthorizeKey.InsertKey);
            AuthorizeKey.CanUpdate = HasFunction(AuthorizeKey.UpdateKey);
            AuthorizeKey.CanDelete = HasFunction(AuthorizeKey.DeleteKey);
            AuthorizeKey.CanView = HasFunction(AuthorizeKey.ViewKey);
            AuthorizeKey.CanList = HasFunction(AuthorizeKey.ListKey);
            AuthorizeKey.CanExport = HasFunction(AuthorizeKey.ExportKey);
        }

其中BaseController的控制器基類還定義了判斷用戶是否有某些權限的邏輯,若是沒有沒有權限,就會拋出自定義異常(MyDenyAccessException),代碼以下。

        /// <summary>
        /// 用於檢查方法執行前的權限,若是未受權,返回MyDenyAccessException異常
        /// </summary>
        /// <param name="functionId"></param>
        protected virtual void CheckAuthorized(string functionId)
        {
            if(!HasFunction(functionId))
            {
                string errorMessage = "您未被受權使用該功能,請從新登陸測試或聯繫管理員進行處理。";
                throw new MyDenyAccessException(errorMessage);
            }
        }

有了上面的這些邏輯,咱們在業務控制器基類(BusinessController<B, T>)裏面,就能夠實現對一些基本操做的API的權限控制了。

    /// <summary>
    /// 本控制器基類專門爲訪問數據業務對象而設的基類
    /// </summary>
    /// <typeparam name="B">業務對象類型</typeparam>
    /// <typeparam name="T">實體類類型</typeparam>
    public class BusinessController<B, T> : BaseController
        where B : class
        where T : WHC.Framework.ControlUtil.BaseEntity, new()
    {

        /// <summary>
        /// 插入指定對象到數據庫中
        /// </summary>
        /// <param name="info">指定的對象</param>
        /// <returns>執行操做是否成功。</returns>
        public virtual ActionResult Insert(T info)
        {
            //檢查用戶是否有權限,不然拋出MyDenyAccessException異常
            base.CheckAuthorized(AuthorizeKey.InsertKey);

            bool result = false;
            if (info != null)
            {
                result = baseBLL.Insert(info);
            }
            return Content(result);
        }

        /// <summary>
        /// 更新對象屬性到數據庫中
        /// </summary>
        /// <param name="info">指定的對象</param>
        /// <param name="id">主鍵ID的值</param>
        /// <returns>執行成功返回<c>true</c>,不然爲<c>false</c></returns>
        public virtual ActionResult Update(string id, FormCollection formValues)
        {
            //檢查用戶是否有權限,不然拋出MyDenyAccessException異常
            base.CheckAuthorized(AuthorizeKey.UpdateKey);

            T obj = baseBLL.FindByID(id);
            if (obj != null)
            {
                //遍歷提交過來的數據(多是實體類的部分屬性更新)
                foreach (string key in formValues.Keys)
                {
                    string value = formValues[key];
                    System.Reflection.PropertyInfo propertyInfo = obj.GetType().GetProperty(key);
                    if (propertyInfo != null)
                    {
                        try
                        {
                            // obj對象有key的屬性,把對應的屬性值賦值給它(從字符串轉換爲合適的類型)
                            //若是轉換失敗,會拋出InvalidCastException異常
                            propertyInfo.SetValue(obj, Convert.ChangeType(value, propertyInfo.PropertyType), null);
                        }
                        catch { }
                    }
                }
            }

            bool result = baseBLL.Update(obj, id);
            return Content(result);
        }

 

三、界面元素權限控制

咱們從上面那個Web開發框架的主界面圖能夠看到,裏面對於某個特定的業務,增長、修改、、查看、刪除等操做都放在了EasyUI的DataGrid工具欄裏面了,爲了動態控制用戶能訪問的界面按鈕,咱們須要結合用戶權限集合進行界面呈現,首先咱們把ToolBar放到一個層裏面進行定義,以下代碼所示。

        //實現對DataGird控件的綁定操做
        function InitGrid(queryData) {
            $('#grid').datagrid({   //定位到Table標籤,Table標籤的ID是grid
                url: '/Information/FindWithPager',   //指向後臺的Action來獲取當前用戶的信息的Json格式的數據
                title: '通知公告',
                iconCls: 'icon-view',
                height: 650,
                width: function () { return document.body.clientWidth * 0.9 },//自動寬度
                nowrap: true,
                autoRowHeight: true,
                striped: true,
                collapsible: true,
                pagination: true,
                pageSize: 50,
                pageList: [50, 100, 200],
                rownumbers: true,
                //sortName: 'ID',    //根據某個字段給easyUI排序
                sortOrder: 'asc',
                remoteSort: false,
                idField: 'ID',
                queryParams: queryData,  //異步查詢的參數
                columns: [[
                     { field: 'ck', checkbox: true },   //選擇
                     { title: '標題', field: 'Title', width: 350, sortable: true },
                     { title: '編輯者', field: 'Editor', width: 80, sortable: true },
                     { title: '編輯時間', field: 'EditTime', width: 150, sortable: true },
                     { title: '附件', field: 'Attachment_GUID', width: 250, sortable: true }
                ]],
                toolbar: "#gridtoolbar",

而後在HTML裏面添加gridtoolbar的層定義,做爲easyUI的表格控件的工具條。因爲使用了HTML輔助類來實現界面控件代碼控制生成,所以已經能夠達到了界面權限的控制了。使用這種HTML層定義的工具條定義方式,比經過腳本定義的工具條效果少了一個分隔線,其餘的都仍是一致的。

    <div id="gridtoolbar" style="padding: 5px; height: auto">
        <div style="margin-bottom: 5px">
            @if (@ViewBag.AuthorizeKey.CanInsert)
            {
                @Html.ActionLink("添加", null, null, new {onclick="ShowAddDialog()", data_options="iconCls:'icon-add', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
            }
            @if (@ViewBag.AuthorizeKey.CanUpdate)
            {
                @Html.ActionLink("修改", null, null, new {onclick="ShowEditOrViewDialog()", data_options="iconCls:'icon-edit', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
            }
            @if (@ViewBag.AuthorizeKey.CanDelete)
            {
                @Html.ActionLink("刪除", null, null, new {onclick="Delete()", data_options="iconCls:'icon-remove', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
            }
            @if (@Html.HasFunction("Information/View"))
            {
                @Html.ActionLink("查看", null, null, new {onclick="ShowEditOrViewDialog('view')", data_options="iconCls:'icon-table', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"})
            }
            @Html.ActionLink("刷新", null, null, new {onclick="$('#grid').datagrid('reload');", data_options="iconCls:'icon-reload', plain:true", @class = "easyui-linkbutton", href="javascript:void(0)"}) 
                      
        </div>
    </div>

上面使用了兩種方式來判斷用戶的權限的,一種是使用這種ViewBag對象的樹形進行判斷,以下所示。

@if (@ViewBag.AuthorizeKey.CanDelete)

還有一種是使用HTML輔助類的擴展方法進行判斷,這種方法適用於一些很是規的權限控制集合的判斷,以下所示

@if (@Html.HasFunction("Information/View"))

其中HTML輔助類方法是經過擴展靜態方法進行實現,代碼以下所示。

    public static class HtmlHelpers
    {
        public static bool HasFunction(this HtmlHelper helper, string functionId)
        {
            return Permission.HasFunction(functionId);
        }

        public static bool IsAdmin()
        {
            return Permission.IsAdmin();
        }
    }

上面的界面控制方法,是經過控制界面代碼的生成與否進行權限控制的,前面咱們講了,經過後臺代碼的控制器方法也是能夠實現控制,並且是拋出自定義的錯誤,那麼咱們在使用Ajax方法調用的時候,也能夠對這個錯誤信息進行友好顯示,提示用戶權限不足,前端頁面操做代碼以下。

        //綁定添加按鈕的事件
        function BindAddEvent() {
            $("#btnAddOK").click(function () {
                //判斷表單的信息是否經過驗證
                var validate = $("#ffAdd").form('validate');
                if (validate == false) {
                    return false;
                }
                
                var postData = $("#ffAdd").serializeArray();
                $.post("/Information/Insert", postData, function (data) {
                    if (data = "true") {
                        //添加成功  1.關閉彈出層,2.刷新DataGird
                        $.messager.alert("提示", "添加成功");
                        $("#DivAdd").dialog("close");
                        $("#grid").datagrid("reload");
                        $("#ffAdd").form("clear");

                        //本頁面的類型爲【通知公告】,固定不變
                        $("#Category").val("通知公告");
                    }
                    else {
                        $.messager.alert("提示", "添加失敗,請您檢查");
                    }
 }).error(function () {
                    $.messager.alert("提示", "您未被受權使用該功能,請聯繫管理員進行處理。", 'warning'); });
            });
        }

 以上就是我對Web開發框架中的權限控制幾個方面的思路和代碼,但願拋磚引玉,得到你們更好的反饋和支持。

 

基於MVC4+EasyUI的Web開發框架的系列文章:

基於MVC4+EasyUI的Web開發框架造成之旅--整體介紹

基於MVC4+EasyUI的Web開發框架造成之旅--MVC控制器的設計

基於MVC4+EasyUI的Web開發框架造成之旅--界面控件的使用

基於MVC4+EasyUI的Web開發框架造成之旅--附件上傳組件uploadify的使用

基於MVC4+EasyUI的Web開發框架造成之旅--框架整體界面介紹

基於MVC4+EasyUI的Web開發框架造成之旅--基類控制器CRUD的操做

基於MVC4+EasyUI的Web開發框架造成之旅--權限控制

基於MVC4+EasyUI的Web開發框架經驗總結(1)-利用jQuery Tags Input 插件顯示選擇記錄

基於MVC4+EasyUI的Web開發框架經驗總結(2)- 使用EasyUI的樹控件構建Web界面

基於MVC4+EasyUI的Web開發框架經驗總結(3)- 使用Json實體類構建菜單數據

基於MVC4+EasyUI的Web開發框架經驗總結(4)--使用圖表控件Highcharts

基於MVC4+EasyUI的Web開發框架經驗總結(5)--使用HTML編輯控件CKEditor和CKFinder

基於MVC4+EasyUI的Web開發框架經驗總結(6)--在頁面中應用下拉列表的處理

基於MVC4+EasyUI的Web開發框架經驗總結(7)--實現省份、城市、行政區三者聯動

基於MVC4+EasyUI的Web開發框架經驗總結(8)--實現Office文檔的預覽

基於MVC4+EasyUI的Web開發框架經驗總結(9)--在Datagrid裏面實現外鍵字段的轉義操做

基於MVC4+EasyUI的Web開發框架經驗總結(10)--在Web界面上實現數據的導入和導出

基於MVC4+EasyUI的Web開發框架經驗總結(11)--使用Bundles處理簡化頁面代碼

基於MVC4+EasyUI的Web開發框架經驗總結(12)--利用Jquery處理數據交互的幾種方式

基於MVC4+EasyUI的Web開發框架經驗總結(13)--DataGrid控件實現自動適應寬帶高度

基於MVC4+EasyUI的Web開發框架經驗總結(14)--自動生成圖標樣式文件和圖標的選擇操做

相關文章
相關標籤/搜索