在现代Web应用中,权限控制是一个核心功能,它确保了用户只能访问他们被授权的资源。本文将介绍一种基于角色的权限控制实现方法,该方法使用.NET MVC和JavaScript技术,为服务器端和客户端提供了一种通用的解决方案。
本系统是一个Web应用,它能够对用户进行认证和授权。用户登录后,包括分配给他们的功能在内的用户详细信息被存储为会话对象。在请求页面时,服务器端和客户端将根据用户的角色和权限来控制访问。
用户登录时,系统会将用户信息和分配给他们的功能存储在会话对象中。当用户请求页面时,服务器端的PermissionFilter
会验证请求;如果用户未认证,则会被重定向到登录页面。同时,PermissionFilter
还会根据用户角色分配的功能/权限来授权请求;如果用户未授权,则会渲染一个NotAuthorized
页面。
在客户端,PermissionChecker
脚本会获取用户被授权的功能的elementControlID
,并从DOM中移除其余元素。
在数据库中,每个功能都有一个唯一的controlID
。在客户端,每个功能都被分配了一个唯一的controlID
,使用属性data-feature="
。在服务器端,通过与请求中的控制器名称和操作名称关联的功能来检查访问权限。
本系统使用Visual Studio 2012和.NET MVC技术构建。
每个用户都有一个角色,每个角色被分配了一个或多个功能/操作(例如:添加/编辑/删除等)。每个功能/权限都是一个由控制器名称和操作名称识别的操作,具有唯一的controlID(Name)
,以便在网页上识别。
在服务器端,有一个动作过滤器,它根据分配给登录用户角色的功能/权限来授权调用的动作。它还验证认证;访问AfterLogin
页面。对于任何未授权的活动,它返回一个NotAuthorizedToUseThisContent
页面。
在客户端,一个脚本会在页面加载和任何Ajax请求完成后,从DOM中移除未分配给登录用户角色的功能。
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())
{
// 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();
}
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
属性,用于过滤对该动作的请求。
这个脚本在页面加载和任何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 {
// Remove element from DOM if not authorized to access it
$(this).addClass("noDisplay");
$(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();
}
});
}
});
});
页面加载和ajaxComplete函数用于DOM过滤。功能仅在页面加载时从服务器获取,并在ajaxComplete中使用,因为如果在ajaxComplete中获取,会再次触发它,最终导致无限循环。
在用户列表页面中,登录用户仅被分配了EditUser
权限,其他功能通过脚本从页面中移除。