基于角色的安全架构使用 .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 中,并在脚本中后续使用。