基于角色的安全架构使用 .NET MVC 和 JS






4.41/5 (12投票s)
一种自定义安全架构,用于页面组件的基于角色的访问。
引言
本文介绍了一种基于角色的组件/功能(例如,编辑用户、添加用户、保存更改)按页面进行访问的架构。这是一个使用 .NET MVC 和 JavaScript 在服务器端和客户端进行基于角色授权的通用解决方案。
该应用程序构建为一个 Web 应用程序,用于对用户进行身份验证和授权。
工作原理
- 登录时,用户详细信息(包括分配的功能)将存储在 `session` 对象中。
- thereon 请求页面时- 服务器端:- PermissionFilter验证请求;如果未进行身份验证,用户将被重定向到登录页面。- PermissionFilter还根据分配的功能/权限验证请求;如果未经授权,将呈现 `NotAuthorized` 页面。
- 客户端:- PermissionChecker脚本获取用户授权的 `elementControlID`,并从 DOM 中删除其余元素。
 
幕后
- 数据库:每个功能都有一个唯一的 `controlID`。
- 客户端:使用 `data-feature="<FeatureControlID>"` 属性为每个功能分配唯一的 `controlID`。
- 服务器端:通过请求中的控制器和操作名称,由与功能关联的 `controllerName` 和 `actionName` 来检查功能访问。
工具与技术
- Visual Studio 2012 - .NET MVC
代码代码可以在以下位置找到
背景
数据库架构

数据库架构
- 每个用户都有一个角色。
- 每个角色被分配 1 个或多个功能/操作(示例:添加/编辑/删除等)。
- 每个功能/权限都是一个由 `controllerName` 和 `actionName` 识别的操作,并具有唯一的 `controlID(Name)` 以在网页上识别它。
示例
- 用户 - XYZ 拥有管理员角色
- 角色 - 管理员被分配了编辑、删除功能
- 功能 - 编辑被识别为 `ControllerName`: `Home`, `ActionName`: `EditUser`
Using the Code
这里有两个主要的授权组件
服务器端
- 一个操作过滤器,用于授权针对 `loggedIn` 用户角色分配的功能/权限的操作。
- 它还验证身份验证;仅允许访问 `AfterLogin` 页面。
- 对于任何未经授权的活动,它会返回一个 `NotAuthorizedToUseThisContent` 页面。
客户端
- 一个脚本,用于在页面加载和任何 Ajax 请求完成后,从 DOM 中删除未分配给 `loggedIn` 用户角色的功能。
服务器端 - PermissionFilter.cs
它是一个操作过滤器,并在每个控制器中对操作的请求中运行。它使用 `loggedin` 用户角色允许的功能/权限中的操作和控制器名称进行身份验证和授权。
public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            base.OnActionExecuting(filterContext);
            HttpRequestBase request = filterContext.HttpContext.Request;
            HttpResponseBase response = filterContext.HttpContext.Response;
            if ((request.AcceptTypes.Contains("application/json")) == false)
            {
                if (request.IsAjaxRequest())
                {
                    #region Preventing caching of ajax request in IE browser
                    response.Cache.SetExpires(DateTime.UtcNow.AddDays(-1));
                    response.Cache.SetValidUntilExpires(false);
                    response.Cache.SetCacheability(HttpCacheability.NoCache);
                    response.Cache.SetNoStore();
                    #endregion
                }
                string currentActionName = filterContext.ActionDescriptor.ActionName;
                string currentControllerName = 
                     filterContext.ActionDescriptor.ControllerDescriptor.ControllerName;
                //get LoggedInUser Permissions
                PermissionManager userPermissions = PermissionManager.getPermissions();
                //All Features are allowed for SuperAdmin - disablePermissioning
                if (userPermissions != null)
                {
                    //For logout page
                    filterContext.Controller.ViewBag.LoggedInAdministrator = 
                                                 PermissionManager.GetLoggedInUser();
                    if (PermissionManager.enablePermissioningSystem == true)
                    {
                        //Not all actions are feature in the application
                        bool isCurrentActionAFeature = Service.isFeaturePresentInList
                           (userPermissions.allFeatures, currentControllerName, currentActionName);
                        if (isCurrentActionAFeature)
                        {
                            bool hasPermission = false;
                            hasPermission = Service.isFeaturePresentInList
                              (userPermissions.accessibleFeatures, currentControllerName, 
                               currentActionName);
                            if (!hasPermission)
                            {
                                //return 'not authorized' content
                                filterContext.Result = new ViewResult
                                {
                                    ViewName = "~/Views/shared/unauthorizedactivity.cshtml"
                                };
                            }
                        }
                    }
                }
                //Redirect to login Page
                else
                {
                    filterContext.Controller.TempData["Message"] = "Please login to continue.";
                    filterContext.Result = new RedirectToRouteResult(
                                new System.Web.Routing.RouteValueDictionary 
                                { { "controller", "home" }, { "action", "login" }, { "Area", "" } });
                }
            }
        }
在服务器上,操作被 `PermissionFilter` 属性装饰,用于过滤对该操作的请求。

客户端 - PermissionChecker.js
此脚本在页面加载和任何 Ajax 请求时从 DOM 中删除未经授权的功能元素。
var allowedFeatureList = new Array();
var enablePermission = false;
/*Register ajax complete callback to apply 'Userpermissions' on ajax requests*/
/*Ajax OnComplete START*/
//For - Authorizing after ajax response
$(document).ajaxComplete(function (event, request, settings) {
    var allDataFeatureElements = $('[data-feature]');
    if (enablePermission == true) {
        $(allDataFeatureElements).each(function () {
            var currentfid = $(this).data("feature");
            //Check If the current data-feature element in Page lies in allowed Features list
            var result = $.inArray(currentfid, allowedFeatureList);
            if (result != -1) {
            }
            else {
                //$(this).addClass("noDisplay");
                //Remove element from DOM if not authorized to access it
                $(this).remove();
            }
        });
    }
});
/*Ajax OnComplete END*/
//For - Authorizing after page load
$(function () {
    //To disable caching of ajax requests in IE browsers
    $.ajaxSetup({
        cache: false
    });
    var allDataFeatureElements = $('[data-feature]');
    //Get allowed Features List for login user
    $.getJSON("/home/getAllAccessibleFeatureControlIDJSON", function (data) {
        enablePermission = data.enablePerm;
        if (data.enablePerm == true) {
            for (var i = 0; i < data.controlIDS.length; i++) {
                allowedFeatureList.push(data.controlIDS[i]);
            }
            $(allDataFeatureElements).each(function () {
                var currentfid = $(this).data("feature");
                var result = $.inArray(currentfid, allowedFeatureList);
                if (result != -1) {
                }
                else {
                    //$(this).addClass("noDisplay");
                    $(this).remove();
                }
            });
        }
    });
});
这里使用 `page-load` 和 `ajaxComplete` 函数进行 DOM 过滤。
功能仅在 `page-load` 时从服务器获取,并在 `ajaxComplete` 中使用,因为在 `ajaxComplete` 中获取会再次触发它,从而导致无限循环。

页面上的功能 ID 调用

功能标识

用户列表页面
在上图中,已登录用户仅被分配了 `EditUser` 权限,而其他功能已被脚本从页面中移除。


未授权页面
关注点
这些需求可以通过在每个页面中使用条件语句,并在服务器端过滤来根据角色显示元素来很好地处理。但是,上述架构为此类需求提供了一个易于维护的全局解决方案。
进一步
每次在服务器上请求功能可以最小化,方法是将值存储在 cookie 中,并在脚本中后续使用。

