关于ACL权限控制【ASP.NET MVC2】
关于系统权限控制
一、基本介绍
任何系统都会有ACL权限控制控制体系,网上也有很多资料。
有些小系统就简单的分为几个角色,写死在代码里,如分为:普通用户、管理员等
在系统中判断登陆者的类型,来显示某些操作。
而一些稍微大点的系统绝对不会这么操作的,都会设计成基于角色的权限管理,只要该角色里包含某些功能,你就能使用,而且提供可配置。
大部分会设计成5张表,分别为:
bs_function:功能表,存放基本的功能
bs_role:角色表,定义具体角色名称
bs_role_functions:角色功能表,定义某个角色所包含的功能
bs_user:用户表
bs_user_roles:用户角色表,定义用户属于哪些角色
上面这张图,深色图可以表示出这5张表的关系。。(其他图可以继续扩张后续自定义流程功能)
我们可以通过该用户ID,在bs_user_roles表中查出该用户属于哪些角色,
然后通过角色ID在b_role_functions中找到这些角色对于有哪些功能号,
最后通过这些功能号对于到bs_function表中,这样就实现了基于角色的权限控制
关于权限判断,当我们进去页面后,我们可以得到所访问的 controller和具体的action,来比对读出的功能url比对,如果该用户有这个URL的访问权限则继续,否则就跳转出错页面。。。
二、根据用户权限动态读取菜单
动态菜单就是根据该用户有哪些权限而显示的菜单,没有权限的菜单完全就可以不用显示。
不同权限的用户进入系统后他们的菜单是不一样的,这用就可以简化系统操作啦。。。。。
关于查询出用户有哪些权限,仅仅关联这几张表是不太好的,最好菜单的结构不被改变,
也就是说,当只有三级菜单有权限的时候,该三级菜单所属的二级菜单、一级菜单也要查询出来,不能单单只列出一个三级菜单的。
这里就要分数据库了,如果是oracle 的话,自带了递归查询,如果是sqlsever或者其他小型数据库不支持递归查询的数据库,那就比较麻烦了,这里我使用sqlserver,我查询出来菜单,在系统中递归处理。。
由于bs_function表不单单存放的是菜单,而且还存放了一些流程需要使用的页面,所以在bs_function表中,我增加了一个字段 功能类型(F_TYPE):menu/process
来区分出是菜单还是流程所需要用到的页面。。
实现步骤:
1、建立菜单模型 (Menu.cs)
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Xml;
6
7 namespace H.MySystem.Models.PageModels
8 {
9
10 /// <summary>
11 /// 菜单内容
12 /// </summary>
13 [Serializable]
14 public class MenuContent
15 {
16 private MainMenu _mainMenu;
17
18 public MainMenu MainMenu
19 {
20 get { return _mainMenu; }
21 set { _mainMenu = value; }
22 }
23
24 private XmlDocument _xml;
25
26 public XmlDocument Xml
27 {
28 get { return _xml; }
29 set { _xml = value; }
30 }
31 }
32
33 /// <summary>
34 /// 主菜单类
35 /// </summary>
36 [Serializable]
37 public class MainMenu
38 {
39 private List<Menu> _menudata = new List<Menu>();
40
41 public List<Menu> menudata
42 {
43 get { return _menudata; }
44 set { _menudata = value; }
45 }
46 }
47
48 /// <summary>
49 /// 菜单模型
50 /// </summary>
51 public class Menu
52 {
53 private string iD;
54
55 public string ID
56 {
57 get { return iD; }
58 set { iD = value; }
59 }
60 private string name;
61
62 public string Name
63 {
64 get { return name; }
65 set { name = value; }
66 }
67 private string _text;
68
69 public string text
70 {
71 get { return _text; }
72 set { _text = value; }
73 }
74 private string _param;
75
76 public string param
77 {
78 get { return _param; }
79 set { _param = value; }
80 }
81 private string _resid;
82
83 public string resid
84 {
85 get { return _resid; }
86 set { _resid = value; }
87 }
88 private string _alias;
89
90 public string alias
91 {
92 get { return _alias; }
93 set { _alias = value; }
94 }
95
96 private string _parentid;
97
98 public string parentid
99 {
100 get { return _parentid; }
101 set { _parentid = value; }
102 }
103
104 private List<Menu> _submenu=new List<Menu>();
105
106 public List<Menu> submenu
107 {
108 get { return _submenu; }
109 set { _submenu = value; }
110 }
111 }
112 }
113
2、用户权限读取类 (UserPurview.cs)
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Xml;
6 using H.MySystem.Models.PageModels;
7 using System.Data;
8
9 namespace H.MySystem.Logic.Achieves
10 {
11 /// <summary>
12 /// 用户权限类
13 /// 提供用户菜单返回
14 /// </summary>
15 public class UserPurview:Interfaces.IPurview
16 {
17 //整个菜单查询SQL
18 static string SQL_SELECT_ALL_MENU = "SELECT * FROM BS_FUNCTION where F_TYPE='menu'";
19 //根据权限查询个人菜单
20 static string SQL_SELECT_PERSION_MENU = @"select distinct a.*
21 from bs_function a ,bs_role b,bs_role_functions c,bs_user_roles d
22 where a.F_NO=c.F_NO
23 and b.ID = c.R_ID
24 and d.R_ID=b.ID
25 and d.u_id={0}";
26
27 public H.MySystem.Models.PageModels.MenuContent GetAllMenu()
28 {
29 return null;
30 }
31
32 /// <summary>
33 /// 根据用户ID取得用户菜单
34 /// </summary>
35 /// <param name="userID"></param>
36 /// <returns></returns>
37 public H.MySystem.Models.PageModels.MenuContent GetMenuByUser(string userID)
38 {
39 H.DBF.Achieves.DBSession session = H.DBF.MyDBFactory.GetSession();
40 DataTable dtSource = session.ExecuteQueryDataSet(SQL_SELECT_ALL_MENU).Tables[0];
41 DataTable dtPerson = session.ExecuteQueryDataSet(string.Format(SQL_SELECT_PERSION_MENU,userID)).Tables[0];
42
43 BuildMenu bm = new BuildMenu(dtSource, dtPerson);
44 return bm.GetMenu();
45 }
46 }
47 }
48
这里我读取了所有菜单,和该用户权限的菜单,来递归生成MenuContent对象,
3、递归生成菜单类 (BuildMenu.cs)
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Text;
5 using System.Data;
6 using H.MySystem.Models.PageModels;
7 using System.Xml;
8 using System.Collections;
9
10 using H.MySystem.Models;
11
12 namespace H.MySystem.Logic.Achieves
13 {
14 /// <summary>
15 /// 递归生成菜单树
16 /// </summary>
17 public class BuildMenu
18 {
19 //用户菜单表
20 private DataTable dtP = new DataTable();
21 //所有菜单列表
22 private DataTable dtSource = new DataTable();
23 //最终XML文档
24 XmlDocument xd = new XmlDocument();
25 //已经使用过的节点ID
26 ArrayList usedElement = new ArrayList();
27
28 /// <summary>
29 ///
30 /// </summary>
31 /// <param name="source">整个菜单集合</param>
32 /// <param name="dt">个人动态菜单集合</param>
33 public BuildMenu(DataTable source,DataTable dt)
34 {
35 dtSource = source;
36 dtP = dt;
37 }
38
39 /// <summary>
40 /// 获取到菜单
41 /// </summary>
42 /// <returns></returns>
43 public MenuContent GetMenu()
44 {
45 MainMenu mm = new MainMenu();
46 //新建一个根节点
47 XmlElement xe = xd.CreateElement("Datas");
48 xd.AppendChild(xe);
49
50 BuildAll(dtSource, xe);
51
52 BuildPersonal(dtP, (XmlElement)xd.SelectNodes(@"Datas/Item")[0]);
53
54 XmlElement root = (XmlElement)xd.SelectNodes(@"Datas/Item")[0];
55 if (root != null)
56 {
57 Menu menuTmp ;
58 foreach(var xl in root.ChildNodes)
59 {
60 menuTmp = new Menu();
61 menuTmp.ID= ((XmlElement)xl).Attributes["F_NO"].Value;
62 menuTmp.Name = ((XmlElement)xl).Attributes["F_NAME"].Value;
63 menuTmp.param = ((XmlElement)xl).Attributes["F_URL"].Value + "?step=first";
64 menuTmp.parentid=((XmlElement)xl).Attributes["PARENT_NO"].Value;
65 menuTmp.resid = ((XmlElement)xl).Attributes["F_NO"].Value;
66 menuTmp.alias = ((XmlElement)xl).Attributes["F_NAME"].Value;
67 menuTmp.text = ((XmlElement)xl).Attributes["F_NAME"].Value;
68 mm.menudata.Add(menuTmp);
69
70 BuileSubmenu((XmlElement)xl,menuTmp);
71
72 }
73 }
74
75 MenuContent mc = new MenuContent();
76 mc.MainMenu = mm;
77 mc.Xml = xd;
78 return mc;
79
80 }
81
82 public void BuileSubmenu(XmlElement node,Menu menu)
83 {
84 if (node != null)
85 {
86 Menu menuTmp;
87 foreach (var xl in node.ChildNodes)
88 {
89 menuTmp = new Menu();
90 menuTmp.ID = ((XmlElement)xl).Attributes["F_NO"].Value;
91 menuTmp.Name = ((XmlElement)xl).Attributes["F_NAME"].Value;
92 menuTmp.param = string.IsNullOrEmpty(((XmlElement)xl).Attributes["F_URL"].Value)?"":((XmlElement)xl).Attributes["F_URL"].Value+ "?step=first";
93 menuTmp.parentid = ((XmlElement)xl).Attributes["PARENT_NO"].Value;
94 menuTmp.resid = ((XmlElement)xl).Attributes["F_NO"].Value;
95 menuTmp.alias = ((XmlElement)xl).Attributes["F_NAME"].Value;
96 menuTmp.text = ((XmlElement)xl).Attributes["F_NAME"].Value;
97 menu.submenu.Add(menuTmp);
98
99 BuileSubmenu((XmlElement)xl, menuTmp);
100 }
101 }
102
103 }
104
105 /// <summary>
106 /// 递归添加XML节点
107 /// </summary>
108 /// <param name="dt">数据源</param>
109 /// <param name="node">当前节点</param>
110 protected void BuildAll(DataTable dt, XmlElement node)
111 {
112 XmlElement tmp;
113
114 //查找出符合条件的记录
115 var list = from r in dt.AsEnumerable()
116 select r;
117 if (node != null && node.Attributes["F_NO"] != null)
118 {
119 list = from r in dt.AsEnumerable()
120 where r["PARENT_NO"].ToString().Equals(node.Attributes["F_NO"].Value)
121 select r;
122 }
123
124 foreach (var row in list)
125 {
126 if (!usedElement.Contains(row["F_NO"].ToString()))
127 {
128 usedElement.Add(row["F_NO"].ToString());
129 tmp = xd.CreateElement("Item");
130 tmp.SetAttribute("F_NO", row["F_NO"].ToString());
131 tmp.SetAttribute("F_NAME", row["F_NAME"].ToString());
132 tmp.SetAttribute("F_URL", row["F_URL"].ToString());
133 tmp.SetAttribute("F_TYPE", row["F_TYPE"].ToString());
134 tmp.SetAttribute("PARENT_NO", row["PARENT_NO"].ToString());
135 tmp.SetAttribute("F_ORDER", row["F_ORDER"].ToString());
136 tmp.SetAttribute("F_REMARK", row["F_REMARK"].ToString());
137
138 node.AppendChild(tmp);
139
140 BuildAll(dt, tmp);
141 }
142 }
143 }
144
145 /// <summary>
146 /// 构建动态菜单树
147 /// </summary>
148 /// <param name="dt"></param>
149 /// <param name="dtPersonal"></param>
150 /// <param name="node"></param>
151 protected void BuildPersonal(DataTable dtPersonal, XmlElement node)
152 {
153 var list = from r in dtPersonal.AsEnumerable()
154 select r["F_NO"];
155 if (node != null && node.Attributes["F_NO"] != null)
156 {
157 list = from r in dtPersonal.AsEnumerable()
158 where r["PARENT_NO"].ToString().Equals(node.Attributes["F_NO"].Value)
159 select r["F_NO"];
160 }
161
162 XmlElement xe;
163 for (int i = 0; i < node.ChildNodes.Count; i++)
164 {
165 xe = (XmlElement)node.ChildNodes[i];
166 BuildPersonal(dtPersonal, xe);
167 if (!list.ToArray().Contains(xe.Attributes["F_NO"].Value) &&
168 !xe.HasChildNodes)
169 {
170 node.RemoveChild(xe);
171 i--;
172 }
173 }
174 }
175 }
176 }
177
这里我使用了xmldocument来转换
这样我们的菜单读取基本上是完成了,这个项目我使用了JSON的格式返回给前台js,来生成菜单树。
所以我在 controller里的代码为:
1 public ActionResult GetUserMenu()
2 {
3 H.MySystem.Logic.Achieves.UserPurview up = new H.MySystem.Logic.Achieves.UserPurview();
4 H.MySystem.Models.PageModels.MenuContent mc = up.GetMenuByUser(Logic.Achieves.Login.GetCurrentUser().ID.ToString());
5
6 return Json(mc.MainMenu,JsonRequestBehavior.AllowGet);
7 }
好了,到这里 基本上就完成了动态菜单的读取,剩下的就是前台js生成j菜单树了,
前面介绍了一个 dtree的js类库来生成菜单树,挺不错,大家有空可以试试