基于MVC+EasyUI的Web开发框架形成之旅–权限控制
admin
2023-08-02 16:06:16
0

我在上一篇随笔《基于MVC4+EasyUI的Web开发框架形成之旅–框架总体界面介绍》中大概介绍了基于MVC的Web开发框架的权限控制总体思路。其中的权限控制就是分为“用户登录身份验证”、“控制器方法权限控制”、“界面元素权限控制”三种控制方式,可以为Web开发框架本身提供了很好用户访问控制和权限控制,使得用户界面呈现菜单、Web界面的按钮和内容、Action的提交控制,均能在总体权限功能分配和控制之下。

本篇文章主要细化这三个方面的介绍,重点介绍“控制器方法权限控制”、“界面元素权限控制”这两种权限控制方式。

1、用户登录控制

登录界面如下所示。

其中登录的前台页面代码如下所示,其中可以在登录界面接收验证码(如果必要的话)。

//实现用户登录
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\";
                }
            }
        });
    });
}

用户登录的后台控制器方法如下所示:

/// 
/// 对用户登录的操作进行验证
/// 
/// 用户账号
/// 用户密码
/// 验证码
/// 
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.Instance.VerifyUser(username, password, MyConstants.SystemType, ip, macAddr);
        if (!string.IsNullOrEmpty(identity))
        {
            UserInfo info = BLLFactory.Instance.GetUserByName(username);
            if (info != null)
            {
                result = \"OK\";
                Session[\"UserInfo\"] = info;
                Session[\"Identity\"] = info.Name.Trim();

                #region 取得用户的授权信息,并存储在Session中

                List functionList = BLLFactory.Instance.GetFunctionsByUser(info.ID, MyConstants.SystemType);
                Dictionary functionDict = new Dictionary();
                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 menuList = BLLFactory.Instance.GetTopMenu(MyConstants.SystemType);
            int i = 0;
            foreach (MenuInfo menuInfo in menuList)
            {
                sb.Append(GetMenuItemString(menuInfo, i));
                i++;
            }
            ViewBag.HeaderScript = sb.ToString();//一级菜单代码
        }
        return View();            
    }

2、控制器方法权限控制

我们知道,对页面的权限控制,可以分为前端控制和后台代码的控制,控制器方法的权限控制属于后台代码的控制。为了方便基类代码的权限控制,我们定义一个权限控制键的类,用来记录通用的增加、修改、删除、查看、列表、导出等传统控制元素,代码如下所示。

/// 
/// 定义常用功能的控制ID,方便基类控制器对用户权限的控制
/// 
[DataContract]
[Serializable]
public class AuthorizeKey
{
    #region 常规功能控制ID
    /// 
    /// 新增记录的功能控制ID
    /// 
    public string InsertKey { get; set; }

    /// 
    /// 更新记录的功能控制ID
    /// 
    public string UpdateKey { get; set; }

    /// 
    /// 删除记录的功能控制ID
    /// 
    public string DeleteKey { get; set; }

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

    /// 
    /// 查看明细的功能控制ID
    /// 
    public string ViewKey { get; set; }

    /// 
    /// 导出记录的功能控制ID
    /// 
    public string ExportKey { get; set; } 
    #endregion

    #region 常规权限判断
    /// 
    /// 判断是否具有插入权限
    /// 
    public bool CanInsert { get; set; }

    /// 
    /// 判断是否具有更新权限
    /// 
    public bool CanUpdate { get; set; }

    /// 
    /// 判断是否具有删除权限
    /// 
    public bool CanDelete { get; set; }

    /// 
    /// 判断是否具有列表权限
    /// 
    public bool CanList { get; set; }

    /// 
    /// 判断是否具有查看权限
    /// 
    public bool CanView { get; set; }

    /// 
    /// 判断是否具有导出权限
    /// 
    public bool CanExport { get; set; }

    #endregion

    /// 
    /// 默认构造函数
    /// 
    public AuthorizeKey() { }

    /// 
    /// 常用构造函数
    /// 
    public AuthorizeKey(string insert, string update, string delete, string view = \"\") 
    {
        this.InsertKey = insert;
        this.UpdateKey = update;
        this.DeleteKey = delete;
        this.ViewKey = view;
    }
}

有了这个实体类,我们就可以在控制器的基类BaseController里面实现一些控制逻辑了。首先我们在控制器每次执行方法前,都对权限进行一个转换,并把控制键存储到ViewBage里面,方便前端页面的控制,如下代码所示。

/// 
/// 重新基类在Action执行之前的事情
/// 
/// 重写方法的参数
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()函数是验证登陆用户是否具有相应的权限的。

/// 
/// 对AuthorizeKey对象里面的操作权限进行赋值,用于页面判断
/// 
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),代码如下。

/// 
/// 用于检查方法执行前的权限,如果未授权,返回MyDenyAccessException异常
/// 
/// 
protected virtual void CheckAuthorized(string functionId)
{
    if(!HasFunction(functionId))
    {
        string errorMessage = \"您未被授权使用该功能,请重新登录测试或联系管理员进行处理。\";
        throw new MyDenyAccessException(errorMessage);
    }
}

有了上面的这些逻辑,我们在业务控制器基类(BusinessController)里面,就可以实现对一些基本操作的API的权限控制了。

/// 
/// 本控制器基类专门为访问数据业务对象而设的基类
/// 
/// 业务对象类型
/// 实体类类型
public class BusinessController : BaseController
    where B : class
    where T : WHC.Framework.ControlUtil.BaseEntity, new()
{

    /// 
    /// 插入指定对象到数据库中
    /// 
    /// 指定的对象
    /// 执行操作是否成功。
    public virtual ActionResult Insert(T info)
    {
        //检查用户是否有权限,否则抛出MyDenyAccessException异常
        base.CheckAuthorized(AuthorizeKey.InsertKey);

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

    /// 
    /// 更新对象属性到数据库中
    /// 
    /// 指定的对象
    /// 主键ID的值
    /// 执行成功返回true,否则为false
    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);
    }

3、界面元素权限控制

我们从上面那个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层定义的工具条定义方式,比通过脚本定义的工具条效果少了一个分隔线,其他的都还是一致的。

@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)\"})

上面使用了两种方式来判断用户的权限的,一种是使用这种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开发框架中的权限控制几个方面的思路和代码,希望抛砖引玉,获得大家更好的反馈和支持。

相关内容

热门资讯

Mobi、epub格式电子书如... 在wps里全局设置里有一个文件关联,打开,勾选电子书文件选项就可以了。
定时清理删除C:\Progra... C:\Program Files (x86)下面很多scoped_dir开头的文件夹 写个批处理 定...
500 行 Python 代码... 语法分析器描述了一个句子的语法结构,用来帮助其他的应用进行推理。自然语言引入了很多意外的歧义,以我们...
scoped_dir32_70... 一台虚拟机C盘总是莫名奇妙的空间用完,导致很多软件没法再运行。经过仔细检查发现是C:\Program...
65536是2的几次方 计算2... 65536是2的16次方:65536=2⁶ 65536是256的2次方:65536=256 6553...
小程序支付时提示:appid和... [Q]小程序支付时提示:appid和mch_id不匹配 [A]小程序和微信支付没有进行关联,访问“小...
pycparser 是一个用... `pycparser` 是一个用 Python 编写的 C 语言解析器。它可以用来解析 C 代码并构...
微信小程序使用slider实现... 众所周知哈,微信小程序里面的音频播放是没有进度条的,但最近有个项目呢,客户要求音频要有进度条控制,所...
Apache Doris 2.... 亲爱的社区小伙伴们,我们很高兴地向大家宣布,Apache Doris 2.0.0 版本已于...
python清除字符串里非数字... 本文实例讲述了python清除字符串里非数字字符的方法。分享给大家供大家参考。具体如下: impor...