本文主要描述一個通用的權限系統實現思路與過程。也是對此次制作權限管理模塊的總結。
制作此系統的初衷是為了讓這個權限系統得以“通用”。就是生產一個web系統通過調用這個權限系統(生成的dll文件),
就可以實現權限管理。這個權限系統會管理已生產系統的所有用戶,菜單,操作項,角色分配,權限分配,日志等內容。
實現此功能從正常訪問和非法訪問兩個方面入手。正常訪問即用戶登錄系統后只能看到或操作自己擁有的菜單;非法訪問即
通過拼寫url等途徑訪問系統的某個功能;所以程序除了實現用戶登錄后獲取用戶擁有的菜單權限,更要擋住用戶的非法請求。兩
者缺一不可。
一.概念
實現這個功能主要利用RBAC權限設計模型,英文(Role-Based Access Control)譯為基於角色的權限管理又叫基於角色的
訪問控制。
二.數據庫設計
1.系統表:因為要達到"通用",所以這個表會記錄各個系統。其他用戶、菜單、操作、權限表每條記錄都會對應系統代碼。
字段說明:Code —> 系統標識代碼
SysName —> 系統名稱
2.菜單表:記錄菜單。每個功能當成一個菜單,菜單有url屬性,用戶通過點擊菜單來訪問對應功能;
字段說明:ID —> 主鍵,自增標識
MenuName —> 菜單名稱
PageUrl —> 菜單對應url
PId —> 菜單父級Id
Lv —> 菜單等級,分一級菜單和二級菜單
ControllerAction —> 菜單唯一標識,用來做權限控制
SystemCode —> 系統標識代碼
3.操作表:此表主要是為了判斷用戶是否有來操作某個具體功能,如常用的【刪除】功能等操作都放在這個表里;
字段說明:ID —> 主鍵,自增標識
OprateName —> 操作名稱
OperateCode —> 操作標識代碼
SystemCode —> 系統標識代碼
4.用戶表:記錄所有系統的使用用戶。記錄用戶賬號、密碼等信息;
字段說明:ID —> 主鍵,自增標識
UserName —> 用戶登錄名稱
UserPwd —> 用戶登錄密碼
SystemCode —> 系統標識代碼
5.角色表:這是RBAC設計不可缺少的,記錄角色信息,不同級別的角色擁有不同的權限。給用戶分配角色也用到它;
字段說明:ID —> 主鍵,自增標識
RoleName —> 角色名稱
SystemCode —> 系統標識代碼
6.權限表:存放用戶的權限信息。在這個系統里我的設計是每個菜單,每個操作都對應一個權限記錄。所以有一個字段[ActionType]
來區分是菜單還是操作;
字段說明:ID —> 主鍵,自增標識
PowerName —> 權限名稱
ActionId —> 菜單或操作ID
ActionType —> 區分是菜單或操作
SystemCode —> 系統標識代碼
7.用戶對應角色表:這個表負責關聯用戶和角色的關系。由於初次使用RBAC模型,又為了快速投入使用,因此在這次使用中沒有
考慮一對多的角色。所以在這個系統里用戶和角色都是一對一的關系,每個用戶對應一個角色;
字段說明:ID —> 主鍵,自增標識
UserId —> 用戶ID
RoleId —> 角色ID
SystemCode —> 系統標識代碼
8.角色對應權限表:這個表負責管理角色和權限的關系。角色和權限屬於一對多的關系,每個角色對應多個權限;
字段說明:ID —> 主鍵,自增標識
RoleId —> 角色ID
PowerId —> 權限ID
SystemCode —> 系統標識代碼
9.日志表:這個應該容易理解,日志表記錄用戶操作系統的痕跡,像用戶信息、訪問url、時間等。
字段說明:ID —> 主鍵,自增標識
UserId —> 操作名稱
VisitUrl —> 操作標識代碼
Remark —> 系統標識代碼
CreateTime —> 創建時間
SystemCode —> 系統標識代碼
三.程序設計
上面說了數據庫的構造,接下來講一下此次程序的設計方案。開篇已經提過為了通用此項目最終生成dll文件以便其他系統調用,
當然你也可以做成webservice,還能滿足跨平台。
這個系統不與任何業務系統公用數據訪問層和業務邏輯層。即使兩者使用一個相同的數據庫,這個系統還是擁有獨立的數據訪
問層和業務邏輯層,當然這個情況只限於一個實現權限管理的“權限后台”。
在此我用了三層,添加了一個接口Service。其他系統只能訪問這個接口調用自己需要的方法。這樣做對於系統本身有利於避免
方法被任意調用,功能實現途徑不一。對使用者也簡潔明了。為了達到這個目的可以用關鍵字internal修飾數據訪問層和業務邏輯層
的類。程序結構如下圖:
繼續說說這個接口提供了哪些方法和屬性吧。
1.當前登陸用戶:在系統中不少地方要用到它,比如展示登陸用戶的賬號,角色等,存放登錄用戶可以使用Session,也可使用Redis;
2.登錄/退出方法;
2.登陸用戶有權限訪問的菜單集合:提供使用用戶正常訪問的菜單,例如樹形菜單等;
3.子菜單集合:方法2也可實現,這個只不過多一個服務而已;
4.判斷操作權限的方法:數據庫中有一個操作表,通過操作代碼去權限表里查詢。判斷登陸用戶是否擁有某一個操作的權限,例如
“刪除”功能;
5.日志集合:滿足各個系統查看日志的需要。
四.權限和日志
這里的權限是為了擋住非正常途徑的訪問。實現思路是通過用戶訪問的url跟用戶可以訪問的菜單屬性【ControllerAction】一一
對比。為了便於理解,在此用開發實例說明一下:
我做業務系統時UI框架用的是MVC4.0,他很好的支持Filter。因此在權限系統三層的基礎上加了一個Filter文件夾,創建PowerAttribute
類,提供驗證登錄和驗證權限兩個類。
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class LoginAttribute : ActionFilterAttribute { /// <summary> 登錄驗證 </summary> /// <param name="filterContext"></param> public override void OnActionExecuting(ActionExecutingContext filterContext) { var loginUser = UserService.LoginUser; base.OnActionExecuting(filterContext); if (loginUser.ID <= 0) { filterContext.Result = new RedirectResult("/home/login"); filterContext.Result.ExecuteResult(filterContext); } } }
[AttributeUsage(AttributeTargets.Method, AllowMultiple = false)] public class PowerAttribute : LoginAttribute { /// <summary> 訪問權限驗證 </summary> /// <param name="filterContext"></param> public override void OnActionExecuting(ActionExecutingContext filterContext) { LoginAttribute loginAttribute = new LoginAttribute(); loginAttribute.OnActionExecuting(filterContext); var routeAction = HttpContext.Current.Request.RequestContext.RouteData.Values["Controller"] + "." +
HttpContext.Current.Request.RequestContext.RouteData.Values["Action"]; List<Menu> userOwnMenuList = MenuService.GetUserOwnMenuList().ResultModel as List<Menu> ?? new List<Menu>(); var isHavepermission = userOwnMenuList.FirstOrDefault(m => m.ControllerAction.ToLower() == routeAction.ToLower()) != null; if (!isHavepermission) { HttpContext.Current.Response.Write("您沒有權限訪問"); HttpContext.Current.Response.End(); } } }
訪問【用戶列表】時,只需在對應的Action上添加特性Power即可。
// GET: /User/List // 用戶列表頁 [Power] [Log(Desc = "查看用戶列表")] public ActionResult List(string code = "", int page = 1)
日志實現同上,就不再敘述了。整篇文字偏多,沒有寫實現接口的代碼,是因為這些方法實現都很基礎。不便登大雅之堂。