网站后台权限设计
头一次写博客,可能章法有些乱,大家将就看下吧。
(图太大,建议下载下来看)
一,前言
公司网站的后台是和其它2个同事一起做的,权限这块是最后加上去的,当时是另外一个同事做的。
后来那位同事离职了,后台在不断修改和增加功能的情况下,页面越来越多,原来的权限设计越来越不能满足需求了。
主要是因为原来的权限是根据页面地址用正则匹配的,这样就出现一个问题,页面如果增加或减少一个参数,就要去修改正则,这样显的太繁琐。
于是就想着重新设计一套权限。于是就有了本文。
二,设计思路
我的设计思路也是根据页面地址来判断,但分成两部分。第一部分为不带参数的页面地址,第二部分是页面地址上带的参数。
当用户访问某个页面时
1,先截取不带参数的地址url,再截取地址中的参数对 params
2,然后从数据库中取出当前用户的所有权限,根据第1步取得的url去匹配权限列表list(同一地址可能对应几个不同权限,比如添加和修改为同一个页面,但权限又是分开的),如果能匹配到,则继续看第3步,否则表示用户没有该页面访问权限
3,如果第2步能匹配到权限列表list,则从第1步中取出参数对 params ;循环list,判断每一个权限的参数是否能与params中的参数匹配(正则);如果有一个完全匹配,则说明用户有访问本页面权限。
三,开始设计
我把权限大致分为页面级权限(即能不能访问某页面)和功能级权限(即能不能使用某页面上的某功能,如删除等)
先设计数据库,如下图:
部分权限如下:
四,判断权限
1,页面级权限
如上图,假设当前访问的页面是 http://admin/message/msgDraftList.aspx?t=2&s=1&kk=9
则根据不带参数的URL:admin/message/msgDraftList.aspx可以匹配到6个权限,这时再根据访问时所带的参数 t=2 s=1 kk=9判断应该属于哪个权限
很明显应该匹配 RightID=879的权限,kk=9不参与权限判定,因为权限表中并没有以该参数作为权限判定的依据。
假设上面的地址中参数 s=9,则明显匹配不到任何权限,这时应该判定当前用户没有权限
2,页面上的功能权限
页面上的功能如果也需要设置权限,则设为 Right=882这样的,在页面上手动输入该权限的 Code来访问该权限,如果用户有该权限,则应该为true否则应该为false
3,A页面上链接到B页面的权限
假设A页面上有链接到B页面的<a href='b.aspx'>带我去B页面</a>, 这时可以通过B页面对应的Code判断是否拥有该页面的权限。
五,实现
1,所有后台页面继承自同一页面 AdminPage,在这个页面上判断页面级权限。然后在每个页面上判断功能级权限。
2,实现代码,贴下我的代码吧:
1 using System; 2 using System.Collections.Generic; 3 using System.Linq; 4 using System.Text; 5 using System.Web; 6 using System.Web.UI; 7 using System.Web.UI.WebControls; 8 using System.IO; 9 using System.Text.RegularExpressions; 10 using System.Collections; 11 using VGShop.Utility; 12 using System.Collections.Specialized; 13 namespace VGShop.Utility 14 { 15 /// <summary> 16 /// 登录状态、权限判断 17 /// </summary> 18 public class AdminPage : Page 19 { 20 /// <summary> 21 /// 已登录的管理员 22 /// </summary> 23 protected VGShop.Entity.Admin user; 24 /// <summary> 25 /// 用户权限 26 /// </summary> 27 protected AdminRights UserRight; 28 /// <summary> 29 /// 当前页的权限 30 /// </summary> 31 AdminRights.SystemRight thisRight = AdminRights.SystemRight.None; 32 /// <summary> 33 /// 页面加载之前的事件,主要是实例化已登录的用户和判断权限 34 /// </summary> 35 /// <param name="e"></param> 36 protected override void OnPreLoad(EventArgs e) 37 { 38 base.OnPreLoad(e); 39 user = Session["user"] as VGShop.Entity.Admin; 40 if (user == null) 41 { 42 System.Configuration.AppSettingsReader asr = new System.Configuration.AppSettingsReader(); 43 string loginPage = asr.GetValue("loginPage", typeof(string)).ToString(); 44 Response.Write(string.Format("<script>top.location.href='/admin/{0}';</script>", loginPage)); 45 Response.End(); 46 } 47 List<Entity.Rights> allRighs = new List<Entity.Rights>();//数据库中所有的权限列表 48 user.RightList = this.GetAdminRights(user, ref allRighs); //查询管理员的权限列表 49 bool result = this.CheckUrl(Request.Url, user.RightList, allRighs); // 页面权限 50 if (user.AdminType == 0) //如果是普通管理员,则检查权限 51 { 52 this.UpAdmin(); //检查当前管理员信息是否被修改过 53 if (!result) 54 { 55 string js = "<script>var noneRightTip = {msg:\"<font color='blue'>您没有权限访问本页,请联系管理员!<br />本页地址:\"+location.href+\"</font>\",fun:function(){}};if (parent.$ && parent.$.jBox) {parent.$.jBox.closeTip();parent.$.jBox.error(noneRightTip.msg, \"无权访问\",{closed:noneRightTip.fun,width:500});} else {if(!alert(noneRightTip.msg)){noneRightTip.fun();};}</script>"; 56 Response.Write(js); 57 Response.End(); 58 return; 59 } 60 } 61 UserRight = new AdminRights(user.RightList, user.AdminType == 1); //用户的权限用于页面上 62 UserRight.CurrentPageRight = thisRight; 63 } 64 65 /// <summary> 66 /// 检查地址是否有权限 67 /// </summary> 68 /// <param name="url">地址</param> 69 /// <param name="list">用户的权限列表</param> 70 /// <returns></returns> 71 bool CheckUrl(Uri url, List<Entity.Rights> list, List<Entity.Rights> allRights) 72 { 73 if (url == null) //如果地址为空,则返回false 74 return false; 75 string lastUrl = url.AbsolutePath.TrimStart('/').ToLower(); 76 var rightList = allRights.Where(a => a.Path.TrimStart('/').ToLower() == lastUrl); 77 int count = rightList.Count(); 78 if (count == 0) //根据当前地址没有找到对应的权限时,则本地址没有权限访问 79 return false; 80 NameValueCollection cols = new NameValueCollection(); 81 #region 获取参数对 82 if (!url.Query.IsNullOrWhiteSpace()) 83 { 84 string[] arr = url.Query.TrimStart('?').Split('&'); 85 foreach (var item in arr) 86 { 87 if (!item.IsNullOrWhiteSpace()) 88 { 89 string[] temp = item.Split('='); 90 if (temp.Length == 2) 91 { 92 cols.Add(temp[0].ToLower(), temp[1]); 93 } 94 } 95 } 96 } 97 #endregion 98 #region 验证权限 99 Dictionary<int, int> dic = new Dictionary<int, int>(); //Key:正面循环中的i,Value:i对应的权限匹配的参数个数 100 for (int i = 0; i < count; i++) 101 { 102 int correct = 0; //已经匹配正确的参数数量 103 bool result = false; 104 int ruleContainsKey = 0; 105 var current = rightList.ElementAt(i); //当前循环的权限 106 result = this.CheckParamAndValue(cols, current.Param1, current.Value1, ref ruleContainsKey); //检查参数1 107 if (!result) //不匹配 108 continue; 109 correct += ruleContainsKey; 110 result = this.CheckParamAndValue(cols, current.Param2, current.Value2, ref ruleContainsKey); //检查参数2 111 if (!result) //不匹配 112 continue; 113 correct += ruleContainsKey; 114 result = this.CheckParamAndValue(cols, current.Param3, current.Value3, ref ruleContainsKey); //检查参数3 115 if (!result) //不匹配 116 continue; 117 correct += ruleContainsKey; 118 result = this.CheckParamAndValue(cols, current.Param4, current.Value4, ref ruleContainsKey); //检查参数4 119 if (!result) //不匹配 120 continue; 121 correct += ruleContainsKey; 122 dic.Add(i, correct); 123 } 124 if (dic.Count > 0) 125 { 126 //Response.Write("<script>alert('匹配的权限有"+dic.Count+"个');</script>"); 127 int index = dic.OrderByDescending(a => a.Value).First().Key; //如果有多个相匹配的权限,则取匹配参数最多的一个 128 Entity.Rights right = rightList.ElementAt(index); 129 if (list.Exists(a => a.RightID == right.RightID)) //如果当前筛选出的权限在用户的权限中,则说明用户有权限,否则说明用户没有该权限 130 { 131 thisRight = (AdminRights.SystemRight)Enum.Parse(typeof(AdminRights.SystemRight), right.Code, true); 132 return true; 133 } 134 } 135 #endregion 136 return false; 137 } 138 /// <summary> 139 /// 检查该权限的指定参数是否匹配规则,如果权限中不包含该参数,则false,包含且值不能匹配也为false 140 /// </summary> 141 /// <param name="cols">当前地址请求中的所有参数和参数名</param> 142 /// <param name="key">权限中的参数名</param> 143 /// <param name="rule">权限中的参数值的规则</param> 144 /// <param name="ruleContainsKey">规则中是是否存在该参数</param> 145 /// <returns></returns> 146 bool CheckParamAndValue(NameValueCollection cols, string key, string rule, ref int ruleContainsKey) 147 { 148 ruleContainsKey = 0; 149 bool result = true; //默认匹配 150 if (!key.IsNullOrWhiteSpace()) // 1) 如果权限中该参数不为空 151 { 152 string val = cols.Get(key.ToLower()); 153 if (val != null) // 2) 如果请求的地址中存在该参数 154 { 155 ruleContainsKey = 10; //如果请求的地址中确实存在该参数,则增量为10 156 if (!rule.IsNullOrWhiteSpace()) // 3) 如果权限中规则不为空,则用正则匹配 157 { 158 result = Regex.IsMatch(val, rule, RegexOptions.IgnoreCase); 159 } 160 } 161 else // 2) 如果请求的地址中不存在该参数,则不匹配 162 { 163 result = false; 164 //如果规则为空,或者可以匹配空字符串,说明参数允许为空,此时也认为请求的地址中包含该参数,此时为真 165 if (rule.IsNullOrWhiteSpace() || (!rule.IsNullOrWhiteSpace() && Regex.IsMatch(string.Empty, rule, RegexOptions.IgnoreCase))) 166 { 167 ruleContainsKey = 1;//如果请求的地址中并没有该参数,但因为参数可匹配空字符串,则增量为1 168 result = true; 169 } 170 } 171 } 172 return result; 173 } 174 /// <summary> 175 /// 查询管理员的权限列表 176 /// </summary> 177 /// <param name="user">管理员</param> 178 /// <param name="rights">系统中所有权限的列表(不论当前用户有没有)</param> 179 private List<Entity.Rights> GetAdminRights(Entity.Admin user, ref List<Entity.Rights> rights) 180 { 181 List<Entity.Rights> list = null; 182 if (user.AdminType == 1) 183 { 184 rights = list = VGShop.Factory.BLLFactory.CreateRights().GetList(true); //取得所有权限的列表 185 } 186 else 187 { 188 string key = string.Format("admin_rights_{0}", user.AdminID); 189 list = Common.CacheAccess.GetCache(key) as List<Entity.Rights>; 190 rights = VGShop.Factory.BLLFactory.CreateRights().GetList(true); //取得所有权限的列表 191 if (list == null) 192 { 193 var bll = VGShop.Factory.BLLFactory.CreateRightMaps(); 194 List<Entity.RightMaps> roleMaps = bll.GetList(user.RoleIDList, true); //角色的所有权限 195 List<Entity.RightMaps> userMaps = bll.GetList(user.AdminID, false, true); //用户的所有权限 196 List<Entity.RightMaps> maps = roleMaps.Union(userMaps).Where(a => !userMaps.Exists(b => b.RightID == a.RightID && b.Forbid)).Distinct(a => a.RightID).ToList(); //得到当前管理员最终的权限映射关系 197 list = rights.Where(a => (!a.Lowest && maps.Exists(b => b.RightID == a.RightID)) || a.Lowest).ToList(); //得到当前管理员最终的所有权限 198 Common.CacheAccess.SetCache(key, list); 199 } 200 } 201 return list; 202 } 203 /// <summary> 204 /// 处理页面异常 205 /// </summary> 206 /// <param name="sender"></param> 207 /// <param name="e"></param> 208 protected void Page_Error(object sender, EventArgs e) 209 { 210 Exception ex = Server.GetLastError(); 211 Tools.WriteErrorLog(ex, true); 212 if (ex is HttpRequestValidationException) 213 { 214 Response.Write("<h1 style='margin:100px 0 0 0;text-align:center;top:100px'>发生一个错误!<a href='javascript:history.go(-1)'>后退</a></h1>"); 215 Response.Write("<label style=\"color:Red;\">" + HttpUtility.HtmlEncode(ex.Message) + "</label>"); 216 Server.ClearError(); // 如果不ClearError()这个异常会继续传到Application_Error()。 217 } 218 } 219 220 /// <summary> 221 /// 输出站点地图和禁用缓存 222 /// </summary> 223 /// <param name="writer"></param> 224 protected override void Render(HtmlTextWriter writer) 225 { 226 base.Render(writer); 227 var node = SiteMap.CurrentNode; 228 if (node != null) 229 { 230 string script = string.Format("<script defer='defer'>var pagebar=document.getElementById('titleBar');if(pagebar){{pagebar.innerHTML='<img height=\"20\" src=\"' + location.protocol + '//' + location.host + '/{0}/skin/Default/Images/home.png\" width=\"20\" /><a href=\"#\" title=\"Billion牛仔\">Billion牛仔</a>>><a href=\"#\">{1}</a> >> <a href=\"#\">{2}</a> ';}}</script>", "Admin", node.ParentNode.Title.ClearHtmlTag().ReplaceHtmlTag(), node.Title.ClearHtmlTag().ReplaceHtmlTag()); 231 writer.WriteLine(script); 232 } 233 //禁止缓存 234 Response.Cache.SetCacheability(HttpCacheability.NoCache); 235 Response.Expires = 0; 236 Response.Buffer = true; 237 Response.ExpiresAbsolute = DateTime.Now.AddSeconds(-1); 238 Response.AddHeader("pragma", "no-cache"); 239 Response.CacheControl = "no-cache"; 240 241 } 242 /// <summary> 243 /// 进行信息更新,判断是否进入被修改名单,是:查询最新信息写入Session,否:不操作。 244 /// </summary> 245 void UpAdmin() 246 { 247 List<int> loginUserList = Application["loginUserList"] as List<int> ?? new List<int>(); 248 //是否被修改了 249 if (loginUserList.Contains(user.AdminID)) 250 { 251 user = VGShop.Factory.DALFactory.CreateAdmin().GetModelByLoginName(user.LoginName); 252 loginUserList.Remove(user.AdminID); 253 Application.Lock(); 254 Application["loginUserList"] = loginUserList; 255 Application.UnLock(); 256 Session.Add("user", user); 257 } 258 } 259 } 260 /// <summary> 261 /// 系统权限 262 /// </summary> 263 public class AdminRights 264 { 265 List<Entity.Rights> myRights = null; 266 bool isSuper = false; 267 /// <summary> 268 /// 当前管理员的全部权限 269 /// </summary> 270 public List<Entity.Rights> AllRights 271 { 272 get { return myRights; } 273 } 274 /// <summary> 275 /// 带参数构造参数 276 /// </summary> 277 /// <param name="rights">管理员的权限</param> 278 /// <param name="superAdmin">是否超级管理员</param> 279 public AdminRights(List<Entity.Rights> rights, bool superAdmin) 280 { 281 myRights = rights; 282 isSuper = superAdmin; 283 } 284 /// <summary> 285 /// 根据权限代码判断是否拥有该权限,超级管理员输入任意代码均返回true 286 /// </summary> 287 /// <param name="key">权限代码,不区分大小写,即Rights.Code</param> 288 /// <returns>是否拥有该权限</returns> 289 public bool this[string key] 290 { 291 get 292 { 293 if (isSuper) 294 return true; 295 return myRights.Exists(a => a.Code.Equals(key, StringComparison.CurrentCultureIgnoreCase)); 296 } 297 } 298 /// <summary> 299 /// 当前页面权限枚举 300 /// </summary> 301 public SystemRight CurrentPageRight { set; get; } 302 #region 权限枚举 303 /// <summary> 304 /// 权限枚举 305 /// </summary> 306 public enum SystemRight 307 { 308 /// <summary> 309 /// 无权限 310 /// </summary> 311 None 312 #region 下面的枚举是从数据库中读取出来生成的,因为太长,就不贴出来了 313 314 #endregion 315 } 316 #endregion 317 } 318 }
六,总结
优点:访问页面时,参数位置可以随意调整,不参与权限判断的参数可以任意增减,并不影响权限,就是说访问页面地址比较灵活。
同一个页面根据不同参数可以分配成不同权限,像添加/修改这样的页面可以做成一个页面,根据参数来区分权限。
缺点:每增加一个页面,都要到数据库中添加权限代码,权限枚举也要重新生成(不是手动写的,太长了)。
页面上功能级权限任需要在页面上手写权限代码来判断权限,如下图:
1 /// <summary> 2 /// 验证权限 3 /// </summary> 4 private void CheckRight() 5 { 6 this.btnAddItem.Visible = UserRight["goods_goodsAdd"]; 7 this.btnDelete.Visible = UserRight["G_Delete"]; 8 this.btnOn.Visible = UserRight["goods_Check"]; 9 this.btnOff.Visible = UserRight["G_CancelSale"]; 10 this.btnSaveSort.Visible = UserRight["G_SaveSort"]; 11 }