65.9K
CodeProject 正在变化。 阅读更多。
Home

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

starIconstarIconstarIconstarIcon
emptyStarIcon
starIcon

4.41/5 (12投票s)

2015年2月23日

CPOL

3分钟阅读

viewsIcon

30902

一种自定义安全架构,用于页面组件的基于角色的访问。

引言

本文介绍了一种基于角色的组件/功能(例如,编辑用户、添加用户、保存更改)按页面进行访问的架构。这是一个使用 .NET MVC 和 JavaScript 在服务器端和客户端进行基于角色授权的通用解决方案。

该应用程序构建为一个 Web 应用程序,用于对用户进行身份验证和授权。

工作原理

  • 登录时,用户详细信息(包括分配的功能)将存储在 `session` 对象中。
  • thereon 请求页面时
    • 服务器端PermissionFilter 验证请求;如果未进行身份验证,用户将被重定向到登录页面。PermissionFilter 还根据分配的功能/权限验证请求;如果未经授权,将呈现 `NotAuthorized` 页面。
    • 客户端PermissionChecker 脚本获取用户授权的 `elementControlID`,并从 DOM 中删除其余元素。

幕后

  • 数据库:每个功能都有一个唯一的 `controlID`。
  • 客户端:使用 `data-feature="<FeatureControlID>"` 属性为每个功能分配唯一的 `controlID`。
  • 服务器端:通过请求中的控制器和操作名称,由与功能关联的 `controllerName` 和 `actionName` 来检查功能访问。

工具与技术

  • Visual Studio 2012 - .NET MVC
代码

代码可以在以下位置找到

https://github.com/gtush24/MVCAndJSSecurityApp.git

背景

数据库架构

数据库架构
  • 每个用户都有一个角色。
  • 每个角色被分配 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` 属性装饰,用于过滤对该操作的请求。

Actions on Server

客户端 - 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` 中获取会再次触发它,从而导致无限循环。

Feature Request

页面上的功能 ID 调用

Feature Element

功能标识

用户列表页面

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

unauthorized activity page

未授权页面

关注点

这些需求可以通过在每个页面中使用条件语句,并在服务器端过滤来根据角色显示元素来很好地处理。但是,上述架构为此类需求提供了一个易于维护的全局解决方案。

进一步

每次在服务器上请求功能可以最小化,方法是将值存储在 cookie 中,并在脚本中后续使用。

© . All rights reserved.