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);

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

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

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

    作者:胖鸟低飞
    本文版权归作者和博客园共有,欢迎转载,但未经作者同意必须保留此段声明,且在文章页面明显位置给出原文连接,否则保留追究法律责任的权利.
  • 相关阅读:
    413 Request Entity Too Large
    小米3手机上加载相册图片失败的问题
    MVC+Ninject+三层架构+代码生成 -- 总结(99)
    1)python 爬取小说
    102)微信小程序 van-dialog
    101) 微信小程序 input 双向绑定
    MVC 表格按树状形式显示 jstree jqgrid
    1. mvc 树形控件tree + 表格jqgrid 显示界面
    叫號系統
    HTML5 下拉控件绑定数据
  • 原文地址:https://www.cnblogs.com/fatbird/p/Mvc4_defaultWebsecurity.html
Copyright © 2011-2022 走看看