首先说明,本文用的是非常屌丝的办法,针对SQL Server Membership的实现的一个很有局限性的权限模块,而不是真正对ASP.NET Membership本身的拓展。不喜慎入。
一、为什么要给Memebership增加权限系统
我们知道,ASP.NET Membership是基于Role的,其实是个RBAC,没有权限(Rights)功能。也就是说,我们只能够指定某个Role可以做什么事,丧失了一定的灵活性。比如,网站后台有个编辑文章的功能。如果基于Role判断,我只能认为:所有Admin、Editor、Teacher可以编辑文章。在MVC3里,可以用Authorize(Role=”Admin, Editor, Teacher”)的属性做到这一点。然而如果想要动态可配,则比较难了。然而,如果是基于Rights的验证,那我就可以认为:所有具有Edit权限的Role可以编辑文章。这样的话,如果那天我们不希望Teacher编辑文章,就可以把Teacher这个Role的Edit权限去掉,而不用改程序。下面我们看看如何在SQL Server上增加一个Rights功能。
二、数据库更改
假设你已经在SQL Server上配好了ASP.NET Memebership服务(可以使用aspnet_regsql工具),你就会看到一些列的表,其中有个叫“Roles”(也可能叫aspnet_Roles),我们需要做的是建另外两张表。
“Right”表包括了你应用程序中所有的权限枚举,这里我仅仅需要“RightName”字段来表示,你可以可以按需要增加其他字段。“RoleRight”是一张多对多的表,描述了“Roles have rights”的关系。
当然,你也可能需要在这几张表之间增加关联,并且把DELETE RULE设为CASCADE。不过,如果你实在不喜欢做外键关联也没关系,但最终码程序的时候你需要一个更复杂的查询来完成,文章后面会一起介绍。
创建表的SQL脚本如下:
CREATE TABLE [dbo].[Rights]( [Id] [uniqueidentifier] NOT NULL, [RightName] [nvarchar](150) NOT NULL, CONSTRAINT [PK_Rights] PRIMARY KEY CLUSTERED ( [Id] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO
CREATE TABLE [dbo].[RoleRight]( [RoleId] [uniqueidentifier] NOT NULL, [RightId] [uniqueidentifier] NOT NULL, CONSTRAINT [PK_RoleRight] PRIMARY KEY CLUSTERED ( [RoleId] ASC, [RightId] ASC )WITH (PAD_INDEX = OFF, STATISTICS_NORECOMPUTE = OFF, IGNORE_DUP_KEY = OFF, ALLOW_ROW_LOCKS = ON, ALLOW_PAGE_LOCKS = ON) ON [PRIMARY] ) ON [PRIMARY] GO ALTER TABLE [dbo].[RoleRight] WITH CHECK ADD CONSTRAINT [FK_RoleRight_Rights] FOREIGN KEY([RightId]) REFERENCES [dbo].[Rights] ([Id]) ON UPDATE CASCADE ON DELETE CASCADE GO ALTER TABLE [dbo].[RoleRight] CHECK CONSTRAINT [FK_RoleRight_Rights] GO ALTER TABLE [dbo].[RoleRight] WITH CHECK ADD CONSTRAINT [FK_RoleRight_Roles] FOREIGN KEY([RoleId]) REFERENCES [dbo].[Roles] ([RoleId]) ON UPDATE CASCADE ON DELETE CASCADE GO ALTER TABLE [dbo].[RoleRight] CHECK CONSTRAINT [FK_RoleRight_Roles] GO
接下来,我们插一些测试数据:
如图,这些记录的意思是所有在Administrators这个Role下的User都具有Edit和Create权限。
三、示例代码:ASP.NET MVC3应用程序的代码更改
首先,我们要对MembershipUser类进行拓展,让它支持我们的权限表。假设你用的是Entity Framework,你可以像这样进行拓展:
public static class MembershipExtensions { public static bool HasRight(this MembershipUser user, string[] rights) { var userRole = user.GetRoles(); using (var db = new HammerEntities()) { var query = from role in db.Roles where userRole.Contains(role.RoleName) select role.Rights into temp from right in temp where rights.Contains(right.RightName) select right; return query.Count() == rights.Length; } } public static string[] GetRoles(this MembershipUser user) { return System.Web.Security.Roles.GetRolesForUser(user.UserName); } }
测试一下这个方法,EF会在运行时产生以下SQL:
SELECT [Join1].[Id] AS [Id], [Join1].[RightName] AS [RightName] FROM [dbo].[Roles] AS [Extent1] INNER JOIN ( SELECT [Extent2].[RoleId] AS [RoleId], [Extent3].[Id] AS [Id], [Extent3].[RightName] AS [RightName] FROM [dbo].[RoleRight] AS [Extent2] INNER JOIN [dbo].[Rights] AS [Extent3] ON [Extent3].[Id] = [Extent2].[RightId] ) AS [Join1] ON [Extent1].[RoleId] = [Join1].[RoleId] WHERE ([Join1].[RightName] IN (N'Edit', N'Create')) AND (N'Administrators' = [Extent1].[RoleName])
结果正如我们希望的一样,Administrators的所有权限被列出来了:
刚才在第二节最后提到,如果你不希望给表之间建立关联,必须使用一个复杂查询来完成同样的操作。这个查询如下,运行结果是一样的:
var query = from role in db.Roles join roleRight in db.RoleRight on role.RoleId equals roleRight.RoleId join right in db.Rights on roleRight.RightId equals right.Id where userRole.Contains(role.RoleName) && rights.Contains(right.RightName) select right;
现在,你的ASP.NET网站已经具有检查权限的功能了:
[Authorize] public ActionResult About() { var rightsToCheck = new string[] { "Edit", "Create" }; if (CheckRight(rightsToCheck)) { return View(); } return new HttpUnauthorizedResult(); } private bool CheckRight(string[] rights) { var user = Membership.GetUser(User.Identity.Name); if (null != user) { return user.HasRight(rights); } return false; }
并且,在ASP.NET MVC3的应用程序中,我们可以用一种更专业的手段去完成验证——作为Attribute。
只要重写AuthorizeAttribute类中的AuthorizeCore()方法,你就可以在MVC3中仅仅用一行代码完成权限验证。RightAuthorizeAttribute代码如下:
public class RightAuthorizeAttribute : AuthorizeAttribute { public string Right { get; set; } protected override bool AuthorizeCore(HttpContextBase httpContext) { return CheckRight(Right.Split(','), httpContext); } private bool CheckRight(string[] rights, HttpContextBase httpContext) { var user = Membership.GetUser(httpContext.User.Identity.Name); if (null != user) { return user.HasRight(rights); } return false; } }
现在,你可以用非常干净的代码来验证权限了:
[Authorize] [RightAuthorize(Right = "Edit,Create")] public ActionResult About() { return View(); }
试试访问“About”Action,因为要求登录,所以系统会弹回一个登录页面:
我是以管理员组的用户登录的,所以可以成功看到About页面的内容:
如果我把权限特征改成[RightAuthorize(Right = "Edit,View")],其中View并不是授权给Administrators的,那么这个Action会再次将我弹回登录页面,因为你没有View的权限了。可以从Debug中看到这一切:
OK,至此我们已经码完了最基本的ASP.NET Memebership权限验证模块~