代码改变世界

[讨论]二、ASP.NET MVC控制到Action参数的权限对策

2010-12-01 14:52  bugfly  阅读(2804)  评论(16编辑  收藏  举报

继上一篇 [讨论]ASP.NET MVC控制到Action参数的权限对策 后,我总觉得我的做法是不正确的,但走不出误区,昨晚和程序网友们讨论后,找出了一些让我放弃这种做法的理由。

 

 1.过分依赖某一个MVC架构的权限设计,不够通用。

 2.权限控制过于复杂。

 3.最关键是有种莫名其妙的别扭感。

 

好了,让我继续尝试下去,失败下去吧。以下的内容有一部分操作是基于上一篇提及到的,我就不在这里描述了。

 

Model First,这次类图比上一次简洁了很多,只有一个Role角色类。

 

接着我们看看AccountController,这是账号管理的地方。

代码
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.Mvc;
6 using Domain;
7 using Repository;
8 using Moq;
9 using WebUI.Views.Account;
10
11 namespace WebUI.Controllers
12 {
13 public class AccountController : Controller
14 {
15 private IUserRepository<SysUser> _Repository;
16
17 //
18 // GET: /Account/
19 [HttpGet]
20 public ActionResult Login()
21 {
22 return View();
23 }
24
25 [HttpPost]
26 public ActionResult Login(Account_LoginVM FormUser)
27 {
28 //初始化模拟数据
29 this.initMockData();
30
31 SysUser DbUser = this._Repository.findByUserID(FormUser.UserID);
32
33 if (DbUser == null)
34 {
35 HttpContext.Response.Write("不存在此用户!");
36 HttpContext.Response.End();
37 }
38 else if (FormUser.UserPwd != DbUser.UserPwd)
39 {
40 HttpContext.Response.Write("密码错误!");
41 HttpContext.Response.End();
42 }
43
44 HttpContext.Session["User"] = DbUser;
45
46 return RedirectToRoute(new { controller = "Home", action = "Index" });
47 }
48
49 private void initMockData()
50 {
51 var mock =new Mock<IUserRepository<SysUser>>();
52
53 //角色链
54 var roleList= new List<UserRole>()
55 {
56 new UserRole()
57 {
58 Id=1,
59 RoleName="Register",
60 },
61 //暂时去掉Admin权限。
62 // new UserRole()
63 //{
64 // Id=2,
65 // RoleName="Admin",
66 //}
67 };
68
69 mock.Setup(m => m.findByUserID("HuntSoul"))
70 .Returns
71 (
72 new SysUser()
73 {
74 Id = 1,
75 UserID = "HuntSoul",
76 UserPwd = "654321",
77 NickName = "BackDoor", //UI Modual,用于ACTION参数验证用的。
78 Roles = roleList
79 }
80 );
81
82 _Repository = mock.Object;
83 }
84 }
85 }
86

和上一篇AccountController没有多少变化,因为去除了几个资源管理类,所以变得更简洁。注意第61行注释的内容,是一个Admin角色,暂时我们先不提供给这个权限。现在我们登陆一下。输入ID:HuntSoul  Pwd:654321  XD不解释~

 

 

结果当然是登录不了啦,因为过滤器对角色和用户账号限制了范围。

 

代码
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.Mvc;
6 using WebUI.ActionFilter;
7
8 namespace WebUI.Controllers
9 {
10 [HandleError]
11 public class HomeController : Controller
12 {
13 //用户通用过滤器,可以配置角色和用户ID的权限组合来过滤。
14 [UserAuthorize
15 (
16 Roles =new string[]{ "Admin"},
17 UserID=new string[]{"Admin"}
18 )
19 ]
20 public ActionResult Index()
21 {
22 ViewData["Message"] = "Index Action 验证成功!";
23
24 return View();
25 }
26
27 //Action级别下参数过滤
28 [FilterByUserInfo
29 (
30 Roles=new string[]{"Admin"}
31 )
32 ]
33 public ActionResult Manage(string Modual)
34 {
35 ViewData["Message"] = "模块进入验证成功!";
36
37 return View();
38 }
39 }
40 }
41

 

 

 相比上一次的过滤器,这次改进了少少。

 

代码
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5 using System.Web.Mvc;
6 using Domain;
7
8 namespace WebUI.ActionFilter
9 {
10 public class UserAuthorizeAttribute:ActionFilterAttribute
11 {
12 /// <summary>
13 /// 角色链
14 /// </summary>
15 public string[] Roles { get; set; }
16
17 /// <summary>
18 /// 用户ID链
19 /// </summary>
20 public string[] UserID { get; set; }
21
22 protected SysUser _User { get; set; }
23
24 public override void OnActionExecuting(ActionExecutingContext filterContext)
25 {
26 _User = HttpContext.Current.Session["User"] as SysUser;
27
28 var rolesResult = from a in _User.Roles
29 from b in Roles
30 where a.RoleName.Equals(b)
31 select a;
32
33 var userIDResult = from a in _User.UserID
34 from b in UserID
35 where a.Equals(b)
36 select a;
37
38 if (rolesResult.ToList().Count == 0 && userIDResult.ToList().Count == 0)
39 {
40 HttpContext.Current.Response.Write("没有权限!");
41 HttpContext.Current.Response.End();
42 }
43 }
44
45 public override void OnResultExecuted(ResultExecutedContext filterContext)
46 {
47 base.OnResultExecuted(filterContext);
48 }
49 }
50 }

 

这次的过滤器,主要功能是,能够对角色或者用户账号进行过滤,基于组合形式:) 好了不废话,我们先去掉Admin角色的注释再一次登录看看。

 

终于登录成功了,好了,废话了那么多终于进入我们一直以来的问题了。可以看到,图片上有几个模块入口,它们指向是同一个Action,但传递的是不同的Action参数,而如果需求是要做权限管理,该如何?这是比较常见的情况,正如上一篇所用的方法,显得太繁琐了,这次的思路是针对特定的Action再写一个特定的过滤器去处理权限分配问题,我们先看看这个过滤器。

 

 

代码
1 using System;
2 using System.Collections.Generic;
3 using System.Linq;
4 using System.Web;
5
6 namespace WebUI.ActionFilter
7 {
8 public class FilterByUserInfoAttribute:UserAuthorizeAttribute
9 {
10 public override void OnActionExecuting(System.Web.Mvc.ActionExecutingContext filterContext)
11 {
12 base.OnActionExecuting(filterContext);
13
14 //配置你想额外做参数权限控制的地方,有点别扭。
15 var moduel = filterContext.RouteData.Values["id"].ToString();
16
17 if (_User.NickName != moduel)
18 {
19 HttpContext.Current.Response.Write("没有权限!");
20 HttpContext.Current.Response.End();
21 }
22 }
23
24 public override void OnActionExecuted(System.Web.Mvc.ActionExecutedContext filterContext)
25 {
26 base.OnActionExecuted(filterContext);
27 }
28 }
29 }

其实这个只不过是针对特定问题的过滤器,可以说,这是唯一一个让我觉得这次方案别扭的地方 XD 。意图大意是如果参数不等于用户某一个属性,如这里的NickName,同样不具有权限查看。同理可以得出对用户其他属性的权限处理。

 

我们看看AccountController的第77行代码,可以发现,我们这里的NickName设置了为BackDoor。。。可能这里的属性用得不恰当,凑合看吧,不影响分析思路。这里BackDoor意味着,如果Action Manage(string modual) 参数不为BackDoor我们就没有权限,我们先看看Index页面的ActionLink.

<%@ Page Language="C#" MasterPageFile="~/Views/Shared/Site.Master" Inherits="System.Web.Mvc.ViewPage" %>

<asp:Content ID="Content1" ContentPlaceHolderID="TitleContent" runat="server">
    Home Page
</asp:Content>

<asp:Content ID="Content2" ContentPlaceHolderID="MainContent" runat="server">
    <h2><%= Html.Encode(ViewData["Message"]) %></h2>
    
    <ul>
       <li><%= Html.ActionLink("游客入口", "Manage", new { id = "Guest" })%></li>
       <li><%= Html.ActionLink("前台管理", "Manage", new { id = "UI" })%></li>
       <li><%= Html.ActionLink("后台管理", "Manage", new { id = "BackDoor" })%></li>
       <li><%= Html.ActionLink("权限管理", "Manage", new { id = "Authority" })%></li>
    </ul>
    
    <p>
        To learn more about ASP.NET MVC visit <a href="http://asp.net/mvc" title="ASP.NET MVC Website">http://asp.net/mvc</a>.
    </p>
</asp:Content>

可以看出,游客入门这条LINK传递的是Guest,而前台管理传递的是UI。。。等等。正常来说我们只能打开传递信息为BackDoor的模块,为了直观点说明问题,我还是把所有情况截了图。

 

点击“游客入口”

点击“前台管理”

点击“后台管理”

好了后面那个不用展示了吧。现在我们把我们的NickName改为UI看看结果。

 

代码
mock.Setup(m => m.findByUserID("HuntSoul"))
.Returns
(
new SysUser()
{
Id
= 1,
UserID
= "HuntSoul",
UserPwd
= "654321",
NickName
= "UI", //UI Modual,用于ACTION参数验证用的。
Roles = roleList
}
);

 

点击“前台模块”模块

点击“后台模块”模块

 

好了,演示流程结束,应该可以说明问题了,还是那句,我只想找一种更好的方法解决这个问题。希望各位尽力拍砖~XD

 

完整Demo