zoukankan      html  css  js  c++  java
  • 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

  • 相关阅读:
    自动化测试===【转】Robot Framework作者建议如何选择自动化测试框架
    python实战===一行代码就能搞定的事情!
    python实战===石头剪刀布,简单模型
    python基础===取txt文件的若干行到另一个文件
    python基础===文件对象的访问模式,以及计数循环的使用方法
    linux===linux在线模拟器汇总
    python基础===两个list合并成一个dict的方法
    python基础===map和zip的用法
    python基础===正则表达式(转)
    python基础===python内置函数大全
  • 原文地址:https://www.cnblogs.com/superfeeling/p/5220057.html
Copyright © 2011-2022 走看看