首先说明,本文用的是非常屌丝的办法,针对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权限验证模块~