Asp.net Mvc4默认权限详细(下)

前言

菜鸟去重复之Sql的问题还没有得到满意的答案。如果哪位大哥有相关的资料解释,能够分享给我,那就太谢谢了。

以后每发表一篇博文我都会将以前遗留的问题在前言里指出,直到解决为止。

本文主要在于探讨一下Asp.net Mvc4默认生成的权限的详细内容。

Asp.net Mvc4默认权限详细(上)的续集。

本文篇幅贴的代码有点多,难免枯燥乏味,奈何水平有限,不贴不行,还请见谅!

无可奈何的表名

还记得这张图片不

是不是感觉这些表名看起来很不爽,非要有个webpages前缀。

于是我第一时间想到是不是有方法来设置这些表名。在上篇博客我们已经知道了是

WebSecurity.InitializeDatabaseConnection("DefaultConnection", "UserProfile", "UserId", "UserName", autoCreateTables: true);

这行代码内部创建了上图的四个表。而这个方法内部是如何创建这些表的呢?我也不知道。反编译吧!反编译的代码有点多,我把主要的贴出来然后一步步分析下

 1         private static void InitializeProviders(DatabaseConnectionInfo connect, string userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables)
 2         {
 3             SimpleMembershipProvider simpleMembership = Membership.Provider as SimpleMembershipProvider;
 4             if (simpleMembership != null)
 5             {
 6                 InitializeMembershipProvider(simpleMembership, connect, userTableName, userIdColumn, userNameColumn, autoCreateTables);
 7             }
 8             SimpleRoleProvider provider = Roles.Provider as SimpleRoleProvider;
 9             if (provider != null)
10             {
11                 InitializeRoleProvider(provider, connect, userTableName, userIdColumn, userNameColumn, autoCreateTables);
12             }
13             Initialized = true;
14         }
15 
16         internal static void InitializeMembershipProvider(SimpleMembershipProvider simpleMembership, DatabaseConnectionInfo connect, string userTableName, string userIdColumn, string userNameColumn, bool createTables)
17         {
18             if (simpleMembership.InitializeCalled)
19             {
20                 throw new InvalidOperationException(WebDataResources.Security_InitializeAlreadyCalled);
21             }
22             simpleMembership.ConnectionInfo = connect;
23             simpleMembership.UserIdColumn = userIdColumn;
24             simpleMembership.UserNameColumn = userNameColumn;
25             simpleMembership.UserTableName = userTableName;
26             if (createTables)
27             {
28                 simpleMembership.CreateTablesIfNeeded();
29             }
30             else
31             {
32                 simpleMembership.ValidateUserTable();
33             }
34             simpleMembership.InitializeCalled = true;
35         }

 在InitializeDatabaseConnection方法中调用了InitializeProviders方法(里面的SimpleMembership和SimpleRoleProvider后面会说到)。

我们还是直接看26-33行,createTables是bool型,就是我们的autoCreateTables参数,只是换了个变量名。

从上我们可以知道如果设置autoCreateTables为true,调用simpleMembership.CreateTablesIfNeeded方法。反之,调用simpleMembership.ValidateUserTable方法。下来让我们看下我们需要的CreateTablesIfNeeded实现

 1         internal void CreateTablesIfNeeded()
 2         {
 3             using (IDatabase database = this.ConnectToDatabase())
 4             {
 5                 if (!CheckTableExists(database, this.UserTableName))
 6                 {
 7                     database.Execute("CREATE TABLE " + this.SafeUserTableName + "(" + this.SafeUserIdColumn + " int NOT NULL PRIMARY KEY IDENTITY, " + this.SafeUserNameColumn + " nvarchar(56) NOT NULL UNIQUE)", new object[0]);
 8                 }
 9                 if (!CheckTableExists(database, OAuthMembershipTableName))
10                 {
11                     database.Execute("CREATE TABLE " + OAuthMembershipTableName + " (Provider nvarchar(30) NOT NULL, ProviderUserId nvarchar(100) NOT NULL, UserId int NOT NULL, PRIMARY KEY (Provider, ProviderUserId))", new object[0]);
12                 }
13                 if (!CheckTableExists(database, MembershipTableName))
14                 {
15                     database.Execute("CREATE TABLE " + MembershipTableName + " (\r\n                        UserId                                  int                 NOT NULL PRIMARY KEY,\r\n                        CreateDate                              datetime            ,\r\n                        ConfirmationToken                       nvarchar(128)       ,\r\n                        IsConfirmed                             bit                 DEFAULT 0,\r\n                        LastPasswordFailureDate                 datetime            ,\r\n                        PasswordFailuresSinceLastSuccess         int                 NOT NULL DEFAULT 0,\r\n                        Password                                nvarchar(128)       NOT NULL,\r\n                        PasswordChangedDate                     datetime            ,\r\n                        PasswordSalt                            nvarchar(128)       NOT NULL,\r\n                        PasswordVerificationToken               nvarchar(128)       ,\r\n                        PasswordVerificationTokenExpirationDate datetime)", new object[0]);
16                 }
17             }
18         }

1-17行  我们一眼就看出来,是这个方法使用Sql创建表,而表名正是其中的几个变量:SafeUserTableName,OAuthMembershipTableName,MembershipTableName。而这几个变量是什么样的!?

 1        private string SafeUserTableName
 2         {
 3             get
 4             {
 5                 return ("[" + this.UserTableName + "]");
 6             }
 7         }
 8 
 9        public string UserTableName { get; set; }
10 
11         internal static string OAuthMembershipTableName
12         {
13             get
14             {
15                 return "webpages_OAuthMembership";
16             }
17         }
18 
19         internal static string MembershipTableName
20         {
21             get
22             {
23                 return "webpages_Membership";
24             }
25         }

这下我们终于知道了OAuthMembershipTableNameMembershipTableName是没有Set方法的,而UserTableName可以Set。所以想通过这种途径改表名的想法不可行了。

WebSecurity

在我们创建的带有权限的MVC4项目中,在AccountController里我们见到的很多的WebSecurity,登录注册注销内部都调用了WebSercurity的静态方法。而这些方法又是如何实现的,内部都干了些什么呢?

 MSDN上有WebSecurity的解释,其中有这么几句

WebSecurity 类在后台与 ASP.NET 成员资格提供程序交互,后者可完成执行安全任务所需的低级工作。ASP.NET Web Pages 中的默认成员资格提供程序为 SimpleMembershipProvider

WebSecurity 类不包括用于创建角色和向用户分配角色的功能。

如果你不想将 WebSecurity 类用于自己的网站,则必须将网站配置为使用标准 ASP.NET 成员资格和角色提供程序。此外,你不得调用 InitializeDatabaseConnection() 方法。将仍然加载 SimpleMembershipProviderSimpleRoleProvider 类,但会将方法和属性调用传递给标准成员资格和角色提供程序。

WebSecuritySimpleMembershipProvider 仅实现哈希选项,该选项被视为这些选项中最安全的选项。因此,WebSecurity 不允许你恢复用户的密码;WebSecurity 限制密码恢复选项的目的,是让你为用户创建新密码。

 也就是说其实WebSecurity内部使用的使用的登录注册注销等的一些方法其实是直接调用SimpleMembershipProvider的方法(反编译也可以印证这一点)。

当然WebSecurity和SimpleMembershipProvider还是有一些区别的,有兴趣的童鞋可以点开链接或者反编译自己比较下,此处就多说了。

那我们想要知道登录注册注销的内部方法,就可以直接去看SimpleMemberShipProvider了。

SimpleMemberShipProvider

这里,我们可以看到MSDN里对这个类的解释

WebSecurity 帮助器类是建议用于管理用户(成员资格)帐户、密码和执行其他成员资格任务的方法。

SimpleMembershipProvider 类可以管理成员资格任务;

但是,由于 WebSecurity 提供了一种实现成员资格的更简单方法,因此不建议使用该类。

SimpleMembershipProvider 类旨在供需要对成员资格进程实施更准确控制的开发人员使用

从上我们知道,虽然WebSecurity和SimpleMemebershipProvider功能差不多,但WebSecurity更方便简洁,微软建议使用WebSecurity。如果想有更精确地控制则使用SimpleMembershipProvider。

好吧!我们还是继续我们的分析吧!此处以注册里的创建账户为例。

CreateUserAndAccount(String username, String password, Boolean requireConfirmationToken)创建新的用户配置文件和新的成员资格帐户。

参数解释

userName  用户名  password 密码

requireConfirmationToken (可选)若指定必须确认用户帐户,则为 true;否则为 false。默认值为 false。

返回string  可以发送给用户以确认用户帐户的令牌。

 1         public override string CreateAccount(string userName, string password, bool requireConfirmationToken)
 2         {
 3             this.VerifyInitialized();
 4             if (password.IsEmpty())
 5             {
 6                 throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword);
 7             }
 8             string str = Crypto.HashPassword(password);
 9             if (str.Length > 0x80)
10             {
11                 throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword);
12             }
13             if (userName.IsEmpty())
14             {
15                 throw new MembershipCreateUserException(MembershipCreateStatus.InvalidUserName);
16             }
17             using (IDatabase database = this.ConnectToDatabase())
18             {
19                 int num = GetUserId(database, this.SafeUserTableName, this.SafeUserNameColumn, this.SafeUserIdColumn, userName);
20                 if (num == -1)
21                 {
22                     throw new MembershipCreateUserException(MembershipCreateStatus.ProviderError);
23                 }
24                 object obj2 = database.QuerySingle("SELECT COUNT(*) FROM [" + MembershipTableName + "] WHERE UserId = @0", new object[] { num });
25                 if (<CreateAccount>o__SiteContainer22.<>p__Site23 == null)
26                 {
27                     <CreateAccount>o__SiteContainer22.<>p__Site23 = CallSite<Func<CallSite, object, bool>>.Create(Binder.UnaryOperation(CSharpBinderFlags.None, ExpressionType.IsTrue, typeof(SimpleMembershipProvider), new CSharpArgumentInfo[] { CSharpArgumentInfo.Create(CSharpArgumentInfoFlags.None, null) }));
28                 }
29                 if (<CreateAccount>o__SiteContainer22.<>p__Site23.Target(<CreateAccount>o__SiteContainer22.<>p__Site23, ((dynamic) obj2)[0] > 0))
30                 {
31                     throw new MembershipCreateUserException(MembershipCreateStatus.DuplicateUserName);
32                 }
33                 string str2 = null;
34                 object obj3 = DBNull.Value;
35                 if (requireConfirmationToken)
36                 {
37                     str2 = GenerateToken();
38                     obj3 = str2;
39                 }
40                 int num2 = 0;
41                 if (database.Execute("INSERT INTO [" + MembershipTableName + "] (UserId, [Password], PasswordSalt, IsConfirmed, ConfirmationToken, CreateDate, PasswordChangedDate, PasswordFailuresSinceLastSuccess) VALUES (@0, @1, @2, @3, @4, @5, @5, @6)", new object[] { num, str, string.Empty, !requireConfirmationToken, obj3, DateTime.UtcNow, num2 }) != 1)
42                 {
43                     throw new MembershipCreateUserException(MembershipCreateStatus.ProviderError);
44                 }
45                 return str2;
46             }
47         }

上面贴出的并不是完整的CreateUserAndAccount方法实现,只是里面调用的一个主要方法。还有一个CreateUserRow方法。

在调用CreateAccount方法之前会调用CreateUserRow方法,在我们自己创建的UserProfile或者自动创建的UserTable中插入我们要创建的UserName(当然支持插入其他字段,有个字典类型参数用来传递我们自己定义的字段)。

然后调用这个CreateAccount方法在自动创建的webpages_Membership表中插入用户的注册名、密码、时间等详细信息(41行)。

我们可以清楚地看到我们创建用户的时候内部是使用的Sql语句执行的。并没有多么复杂的逻辑,就是在自动创建的成员资格表里进行的增删改一系列的操作。

而在我们实际使用(简单方便)的过程中,需要用到这些表映射实体(webpages_Roles、webpages_Memebership等)的地方很少,就算有也可以通过GetUserId,GetAllUser等的一些方法获取需要的集合或者单值。

其实有时想想,既然微软几乎已经实现了我们需要用到的所有功能,那改不改表名已经无所谓了。改了也几乎用不到了。而且这是内置的权限功能,本身就是作为样板。

如果是简单业务,对权限要求不是特别高的,完全可以胜任。

当然对于要求有更完善苛刻权限的,那就必须得自己去实现一套,SimpleMembership里的解释已经说了,还是有些功能它并没有实现的,并不适合。

SimpleRoleProvider

 先看下MSDN解释

在 ASP.NET Web Pages 网站中,可以通过使用页面的 Roles 属性管理和测试角色。例如,若要确定用户是否属于特定角色,可以调用 Roles.IsUserInRole 方法。

根据设计,如在所有 ASP.NET 角色提供程序使用的 RoleProvider 基类中定义的那样,SimpleRoleProvider 类并不实现可以在 ASP.NET 角色提供程序中实现的所有功能。如果你的网站需要全部角色提供程序功能,则可以跳过 Web Pages 角色系统的初始化(也就是说,不调用 WebSecurity.InitializeDatabaseConnection()),然后启用标准成员资格和角色提供程序。在这种情况下,对 SimpleRoleProvider 类的调用将传递给标准提供程序(在 SimpleRoleProvider 类文档中称为“前面的提供程序”)。

在我们最初贴出的第一段代码(InitializeProviders)里,我们可以看到两行获取MembershipProvider和RolePorvider的代码。

SimpleMembershipProvider simpleMembership = Membership.Provider as SimpleMembershipProvider;
 SimpleRoleProvider provider = Roles.Provider as SimpleRoleProvider;

之前已经提到过提供默认的SimpleRoleProvider和SimpleMemberShip。Memebership和Roles都有一个只包含get方法的Provider(对应的MembershipProvider和RoleProvider)属性。而它们内部的公共静态方法使用的都调用Provider提供的方法。那么初始化默认就是调用SimpleMembership和SimpleRoleProvider。也就是说下面几行代码是等效的。

1                     ///直接使用Merbership.Provider
2                     SimpleMembershipProvider testProvider = Membership.Provider as SimpleMembershipProvider;
3                     testProvider.CreateUserAndAccount(model.UserName, model.Password);
4 //使用WebSecurity 5 WebSecurity.CreateUserAndAccount(model.UserName, model.Password);
6 //如果Membership存在CreateUserAndAccount方法并实现了(其实没有实现) 7 Membership.CreateUserAndAccount(model.UserName, model.Password);

RoleProvider和MembershipProvider使用方式差不多。只是Membership内部很多方法都没有实现,而Roles很多需要用到的都实现了。所以一般使用默认的权限需要用到两个

静态类。一个是Websecurity,一个是Roles。Roles的具体方法大家可以打开链接看,此处就不一一说明了。

MSDN上解释的这句--不调用 WebSecurity.InitializeDatabaseConnection()),然后启用标准成员资格和角色提供程序”。在这种情况下,对 SimpleRoleProvider 类的调用将传递给标准提供程序(在 SimpleRoleProvider 类文档中称为“前面的提供程序”)。是什么意思呢?反编译!!!!

 1         public SimpleRoleProvider(RoleProvider previousProvider)
 2         {
 3             this._previousProvider = previousProvider;
 4         }
 5 
 6         public override void CreateRole(string roleName)
 7         {
 8             if (!this.InitializeCalled)
 9             {
10                 this.PreviousProvider.CreateRole(roleName);
11             }
12             else
13             {
14                 using (IDatabase database = this.ConnectToDatabase())
15                 {
16                     if (FindRoleId(database, roleName) != -1)
17                     {
18                         throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, WebDataResources.SimpleRoleProvider_RoleExists, new object[] { roleName }));
19                     }
20                     if (database.Execute("INSERT INTO " + RoleTableName + " (RoleName) VALUES (@0)", new object[] { roleName }) != 1)
21                     {
22                         throw new ProviderException(WebDataResources.Security_DbFailure);
23                     }
24                 }
25             }
26         }

1-4行 是SimpleRoleProvider的一个构造方法,可以传入你自己定制的RoleProvider。

8行 this.InitializeCalled就是判断是否初始化过SimpleRoleProvider,本文贴出的第一段代码里面有SimpleMermbership的InitializeCalled设置。

8-26行 你就会蛋疼的发现,它要进行判断来决定是使用你自己定制的RoleProvider还是SimpleRoleProvider。而只要你不调用Websecurity.InitializeDatabaseConnection

或者指定InitializeCalled=true。它就会调用你实现的相关方法。此处我也有点不理解,如果已经自己定制了,直接自己把工作都做完了,还要麻烦地去new SimpleRoleProvider,太蛋疼了吧!我觉得还是直接new 自己的不就行了,还要绕这么个圈子用SimpleRoleProvider。

总结

我确信我写的篇幅有点长了(贴的代码很多,但不贴又不好表述),至少我感觉是这样。

废话了这么多,也不知道大家能从中了解到自己想了解的吗。

呵呵!至少我从头到尾几乎都没有提到如何去实现自己的成员角色控制,这个可能还要看以后在项目中熟练使用了再来分享给大家。

我这里有个链接实现自己的MembershipProvider的,大家可以参考下,如果你觉得微软提供的默认权限不理想的话。

反编译的感觉真的很不错,你可以学到很多东西!

本篇文章与上篇都只是稍微详细的分析了下Asp.net Mvc4的模版权限内容。

我的水平有限,如果文章中有分析不对的地方,还请指出来,大家共同探讨进步哈!

我发现了一条菜鸟奔MVC大牛的资料链接,在这里分享给大家了。

写文章不容易,如果对您有用,那就推荐一下吧!

posted @ 2013-07-01 00:13  胖鸟低飞  阅读(6182)  评论(8编辑  收藏  举报