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

前言

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

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

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

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

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

无可奈何的表名

还记得这张图片不

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

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

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

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

private static void InitializeProviders(DatabaseConnectionInfo connect, string userTableName, string userIdColumn, string userNameColumn, bool autoCreateTables)
        {
            SimpleMembershipProvider simpleMembership = Membership.Provider as SimpleMembershipProvider;
            if (simpleMembership != null)
            {
                InitializeMembershipProvider(simpleMembership, connect, userTableName, userIdColumn, userNameColumn, autoCreateTables);
            }
            SimpleRoleProvider provider = Roles.Provider as SimpleRoleProvider;
            if (provider != null)
            {
                InitializeRoleProvider(provider, connect, userTableName, userIdColumn, userNameColumn, autoCreateTables);
            }
            Initialized = true;
        }

        internal static void InitializeMembershipProvider(SimpleMembershipProvider simpleMembership, DatabaseConnectionInfo connect, string userTableName, string userIdColumn, string userNameColumn, bool createTables)
        {
            if (simpleMembership.InitializeCalled)
            {
                throw new InvalidOperationException(WebDataResources.Security_InitializeAlreadyCalled);
            }
            simpleMembership.ConnectionInfo = connect;
            simpleMembership.UserIdColumn = userIdColumn;
            simpleMembership.UserNameColumn = userNameColumn;
            simpleMembership.UserTableName = userTableName;
            if (createTables)
            {
                simpleMembership.CreateTablesIfNeeded();
            }
            else
            {
                simpleMembership.ValidateUserTable();
            }
            simpleMembership.InitializeCalled = true;
        }

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

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

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

internal void CreateTablesIfNeeded()
        {
            using (IDatabase database = this.ConnectToDatabase())
            {
                if (!CheckTableExists(database, this.UserTableName))
                {
                    database.Execute("CREATE TABLE " + this.SafeUserTableName + "(" + this.SafeUserIdColumn + " int NOT NULL PRIMARY KEY IDENTITY, " + this.SafeUserNameColumn + " nvarchar(56) NOT NULL UNIQUE)", new object[0]);
                }
                if (!CheckTableExists(database, OAuthMembershipTableName))
                {
                    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]);
                }
                if (!CheckTableExists(database, MembershipTableName))
                {
                    database.Execute("CREATE TABLE " + MembershipTableName + " (
                        UserId                                  int                 NOT NULL PRIMARY KEY,
                        CreateDate                              datetime            ,
                        ConfirmationToken                       nvarchar(128)       ,
                        IsConfirmed                             bit                 DEFAULT 0,
                        LastPasswordFailureDate                 datetime            ,
                        PasswordFailuresSinceLastSuccess         int                 NOT NULL DEFAULT 0,
                        Password                                nvarchar(128)       NOT NULL,
                        PasswordChangedDate                     datetime            ,
                        PasswordSalt                            nvarchar(128)       NOT NULL,
                        PasswordVerificationToken               nvarchar(128)       ,
                        PasswordVerificationTokenExpirationDate datetime)", new object[0]);
                }
            }
        }

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

private string SafeUserTableName
        {
            get
            {
                return ("[" + this.UserTableName + "]");
            }
        }

       public string UserTableName { get; set; }

        internal static string OAuthMembershipTableName
        {
            get
            {
                return "webpages_OAuthMembership";
            }
        }

        internal static string MembershipTableName
        {
            get
            {
                return "webpages_Membership";
            }
        }

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

WebSecurity

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

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

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

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

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

WebSecurity 和 SimpleMembershipProvider 仅实现哈希选项,该选项被视为这些选项中最安全的选项。因此,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  可以发送给用户以确认用户帐户的令牌。

public override string CreateAccount(string userName, string password, bool requireConfirmationToken)
        {
            this.VerifyInitialized();
            if (password.IsEmpty())
            {
                throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword);
            }
            string str = Crypto.HashPassword(password);
            if (str.Length > 0x80)
            {
                throw new MembershipCreateUserException(MembershipCreateStatus.InvalidPassword);
            }
            if (userName.IsEmpty())
            {
                throw new MembershipCreateUserException(MembershipCreateStatus.InvalidUserName);
            }
            using (IDatabase database = this.ConnectToDatabase())
            {
                int num = GetUserId(database, this.SafeUserTableName, this.SafeUserNameColumn, this.SafeUserIdColumn, userName);
                if (num == -1)
                {
                    throw new MembershipCreateUserException(MembershipCreateStatus.ProviderError);
                }
                object obj2 = database.QuerySingle("SELECT COUNT(*) FROM [" + MembershipTableName + "] WHERE UserId = @0", new object[] { num });
                if (<CreateAccount>o__SiteContainer22.<>p__Site23 == null)
                {
                    <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) }));
                }
                if (<CreateAccount>o__SiteContainer22.<>p__Site23.Target(<CreateAccount>o__SiteContainer22.<>p__Site23, ((dynamic) obj2)[0] > 0))
                {
                    throw new MembershipCreateUserException(MembershipCreateStatus.DuplicateUserName);
                }
                string str2 = null;
                object obj3 = DBNull.Value;
                if (requireConfirmationToken)
                {
                    str2 = GenerateToken();
                    obj3 = str2;
                }
                int num2 = 0;
                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)
                {
                    throw new MembershipCreateUserException(MembershipCreateStatus.ProviderError);
                }
                return str2;
            }
        }

上面贴出的并不是完整的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。也就是说下面几行代码是等效的。

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

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

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

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

public SimpleRoleProvider(RoleProvider previousProvider)
        {
            this._previousProvider = previousProvider;
        }

        public override void CreateRole(string roleName)
        {
            if (!this.InitializeCalled)
            {
                this.PreviousProvider.CreateRole(roleName);
            }
            else
            {
                using (IDatabase database = this.ConnectToDatabase())
                {
                    if (FindRoleId(database, roleName) != -1)
                    {
                        throw new InvalidOperationException(string.Format(CultureInfo.InvariantCulture, WebDataResources.SimpleRoleProvider_RoleExists, new object[] { roleName }));
                    }
                    if (database.Execute("INSERT INTO " + RoleTableName + " (RoleName) VALUES (@0)", new object[] { roleName }) != 1)
                    {
                        throw new ProviderException(WebDataResources.Security_DbFailure);
                    }
                }
            }
        }

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大牛的资料链接,在这里分享给大家了。

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

http://www.cnblogs.com/fatbird/p/Mvc4_defaultWebsecurity.html

原文地址:https://www.cnblogs.com/superfeeling/p/5220057.html