頭一次寫博客,可能章法有些亂,大家將就看下吧。
(圖太大,建議下載下來看)
一,前言
公司網站的后台是和其它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 }